diff --git a/CHANGELOG.md b/CHANGELOG.md index 537c6d89c..7c41ae0a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 2.206.0 - 2025-10-04 ### Added @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Localized the number formatting in the settings dialog to customize the rule thresholds of the _X-ray_ page +- Improved the usability of the assistant by preselecting the first search result - Improved the usability of the _Cancel_ / _Close_ buttons in the create watchlist item dialog - Refactored the `fireWealth` from `number` type to a structured object in the summary of the portfolio details endpoint - Refactored the _Open Startup_ (`/open`) page to standalone @@ -22,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Handled an exception in the get asset profile functionality of the _Financial Modeling Prep_ service +- Added the missing `CommonModule` import in the import activities dialog ## 2.205.0 - 2025-10-01 diff --git a/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts index 9811a6564..51c2b8951 100644 --- a/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/rule/rule-settings-dialog/interfaces/interfaces.ts @@ -5,6 +5,7 @@ import { export interface IRuleSettingsDialogParams { categoryName: string; + locale: string; rule: PortfolioReportRule; settings: XRayRulesSettings['AccountClusterRiskCurrentInvestment']; } diff --git a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts index 7ee9c66cf..f8ce13e0d 100644 --- a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts +++ b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts @@ -1,4 +1,5 @@ import { XRayRulesSettings } from '@ghostfolio/common/interfaces'; +import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; import { Component, Inject } from '@angular/core'; @@ -17,6 +18,7 @@ import { IRuleSettingsDialogParams } from './interfaces/interfaces'; imports: [ CommonModule, FormsModule, + GfValueComponent, MatButtonModule, MatDialogModule, MatSliderModule diff --git a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html index 9fdc0cd57..83d4e2d19 100644 --- a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html +++ b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -5,28 +5,30 @@ data.rule.configuration.thresholdMin && data.rule.configuration.thresholdMax ) {
-
+
Threshold range: - @if (data.rule.configuration.threshold.unit === '%') { - {{ data.settings.thresholdMin | percent: '1.2-2' }} - } @else { - {{ data.settings.thresholdMin }} - } - - - @if (data.rule.configuration.threshold.unit === '%') { - {{ data.settings.thresholdMax | percent: '1.2-2' }} - } @else { - {{ data.settings.thresholdMax }} - } + + - +
- @if (data.rule.configuration.threshold.unit === '%') { - - } @else { - - } + - @if (data.rule.configuration.threshold.unit === '%') { - - } @else { - - } +
} @else { @@ -50,22 +51,23 @@ class="w-100" [ngClass]="{ 'd-none': !data.rule.configuration.thresholdMin }" > -
+
Threshold Min: - @if (data.rule.configuration.threshold.unit === '%') { - {{ data.settings.thresholdMin | percent: '1.2-2' }} - } @else { - {{ data.settings.thresholdMin }} - } +
- @if (data.rule.configuration.threshold.unit === '%') { - - } @else { - - } + - @if (data.rule.configuration.threshold.unit === '%') { - - } @else { - - } +
-
+
Threshold Max: - @if (data.rule.configuration.threshold.unit === '%') { - {{ data.settings.thresholdMax | percent: '1.2-2' }} - } @else { - {{ data.settings.thresholdMax }} - } +
- @if (data.rule.configuration.threshold.unit === '%') { - - } @else { - - } + - @if (data.rule.configuration.threshold.unit === '%') { - - } @else { - - } +
} diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts index d1eaf0fe1..2439a4b65 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts @@ -15,6 +15,7 @@ import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper'; +import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, @@ -59,6 +60,7 @@ import { ImportActivitiesDialogParams } from './interfaces/interfaces'; changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'd-flex flex-column h-100' }, imports: [ + CommonModule, GfActivitiesTableComponent, GfDialogFooterComponent, GfDialogHeaderComponent, diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index 57c440bdb..3fc1cc232 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -169,6 +169,8 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }; public tags: Filter[] = []; + private readonly PRESELECTION_DELAY = 100; + private filterTypes: Filter['type'][] = [ 'ACCOUNT', 'ASSET_CLASS', @@ -176,7 +178,9 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { 'SYMBOL', 'TAG' ]; + private keyManager: FocusKeyManager; + private preselectionTimeout: ReturnType; private unsubscribeSubject = new Subject(); public constructor( @@ -344,6 +348,9 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { .subscribe({ next: (searchResults) => { this.searchResults = searchResults; + + this.preselectFirstItem(); + this.changeDetectorRef.markForCheck(); }, error: (error) => { @@ -585,6 +592,10 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { } public ngOnDestroy() { + if (this.preselectionTimeout) { + clearTimeout(this.preselectionTimeout); + } + this.unsubscribeSubject.next(); this.unsubscribeSubject.complete(); } @@ -595,6 +606,58 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }); } + private getFirstSearchResultItem() { + if (this.searchResults.quickLinks?.length > 0) { + return this.searchResults.quickLinks[0]; + } + + if (this.searchResults.accounts?.length > 0) { + return this.searchResults.accounts[0]; + } + + if (this.searchResults.holdings?.length > 0) { + return this.searchResults.holdings[0]; + } + + if (this.searchResults.assetProfiles?.length > 0) { + return this.searchResults.assetProfiles[0]; + } + + return null; + } + + private preselectFirstItem() { + if (this.preselectionTimeout) { + clearTimeout(this.preselectionTimeout); + } + + this.preselectionTimeout = setTimeout(() => { + if (!this.isOpen || !this.searchFormControl.value) { + return; + } + + const firstItem = this.getFirstSearchResultItem(); + + if (!firstItem) { + return; + } + + for (const item of this.assistantListItems) { + item.removeFocus(); + } + + this.keyManager.setFirstItemActive(); + + const currentFocusedItem = this.getCurrentAssistantListItem(); + + if (currentFocusedItem) { + currentFocusedItem.focus(); + } + + this.changeDetectorRef.markForCheck(); + }, this.PRESELECTION_DELAY); + } + private searchAccounts(aSearchTerm: string): Observable { return this.dataService .fetchAccounts({ diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.ts b/libs/ui/src/lib/holdings-table/holdings-table.component.ts index 83faf7621..1c46e18db 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.ts +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.ts @@ -1,4 +1,3 @@ -import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe'; import { getLocale } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, @@ -34,7 +33,6 @@ import { GfValueComponent } from '../value/value.component'; imports: [ CommonModule, GfEntityLogoComponent, - GfSymbolPipe, GfValueComponent, MatButtonModule, MatDialogModule, diff --git a/package-lock.json b/package-lock.json index 8f8676556..2ed25d7c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.205.0", + "version": "2.206.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.205.0", + "version": "2.206.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 28881f546..8717f58df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.205.0", + "version": "2.206.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio",