diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d84426e2..f98d859e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Included accounts in the search results of the assistant + ### Changed - Migrated the prompt dialog component from `ngModel` to form control diff --git a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts index 70b466127..1cfcfec6a 100644 --- a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts +++ b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts @@ -50,20 +50,27 @@ export class GfAssistantListItemComponent public constructor(private changeDetectorRef: ChangeDetectorRef) {} public ngOnChanges() { - if (this.item?.mode === SearchMode.ASSET_PROFILE) { + if (this.item?.mode === SearchMode.ACCOUNT) { + this.queryParams = { + accountDetailDialog: true, + accountId: this.item.id + }; + + this.routerLink = internalRoutes.accounts.routerLink; + } else if (this.item?.mode === SearchMode.ASSET_PROFILE) { this.queryParams = { assetProfileDialog: true, - dataSource: this.item?.dataSource, - symbol: this.item?.symbol + dataSource: this.item.dataSource, + symbol: this.item.symbol }; this.routerLink = internalRoutes.adminControl.subRoutes.marketData.routerLink; } else if (this.item?.mode === SearchMode.HOLDING) { this.queryParams = { - dataSource: this.item?.dataSource, + dataSource: this.item.dataSource, holdingDetailDialog: true, - symbol: this.item?.symbol + symbol: this.item.symbol }; this.routerLink = []; diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index 032b3222d..e5d0dd6da 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -153,14 +153,16 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }); public holdings: PortfolioPosition[] = []; public isLoading = { + accounts: false, assetProfiles: false, holdings: false, quickLinks: false }; public isOpen = false; - public placeholder = $localize`Find holding or page...`; + public placeholder = $localize`Find account, holding or page...`; public searchFormControl = new FormControl(''); public searchResults: ISearchResults = { + accounts: [], assetProfiles: [], holdings: [], quickLinks: [] @@ -199,11 +201,13 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { .pipe( map((searchTerm) => { this.isLoading = { + accounts: true, assetProfiles: true, holdings: true, quickLinks: true }; this.searchResults = { + accounts: [], assetProfiles: [], holdings: [], quickLinks: [] @@ -217,6 +221,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { distinctUntilChanged(), switchMap((searchTerm) => { const results = { + accounts: [], assetProfiles: [], holdings: [], quickLinks: [] @@ -226,6 +231,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { return of(results).pipe( tap(() => { this.isLoading = { + accounts: false, assetProfiles: false, holdings: false, quickLinks: false @@ -234,6 +240,25 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { ); } + // Accounts + const accounts$: Observable> = + this.searchAccounts(searchTerm).pipe( + map((accounts) => ({ + accounts: accounts.slice( + 0, + GfAssistantComponent.SEARCH_RESULTS_DEFAULT_LIMIT + ) + })), + catchError((error) => { + console.error('Error fetching accounts for assistant:', error); + return of({ accounts: [] as ISearchResultItem[] }); + }), + tap(() => { + this.isLoading.accounts = false; + this.changeDetectorRef.markForCheck(); + }) + ); + // Asset profiles const assetProfiles$: Observable> = this .hasPermissionToAccessAdminControl @@ -299,13 +324,14 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { ); // Merge all results - return merge(quickLinks$, assetProfiles$, holdings$).pipe( + return merge(accounts$, assetProfiles$, holdings$, quickLinks$).pipe( scan( (acc: ISearchResults, curr: Partial) => ({ ...acc, ...curr }), { + accounts: [], assetProfiles: [], holdings: [], quickLinks: [] @@ -323,6 +349,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { error: (error) => { console.error('Assistant search stream error:', error); this.searchResults = { + accounts: [], assetProfiles: [], holdings: [], quickLinks: [] @@ -331,6 +358,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }, complete: () => { this.isLoading = { + accounts: false, assetProfiles: false, holdings: false, quickLinks: false @@ -451,12 +479,14 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { public initialize() { this.isLoading = { + accounts: true, assetProfiles: true, holdings: true, quickLinks: true }; this.keyManager = new FocusKeyManager(this.assistantListItems).withWrap(); this.searchResults = { + accounts: [], assetProfiles: [], holdings: [], quickLinks: [] @@ -472,6 +502,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }); this.isLoading = { + accounts: false, assetProfiles: false, holdings: false, quickLinks: false @@ -564,6 +595,34 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }); } + private searchAccounts(aSearchTerm: string): Observable { + return this.dataService + .fetchAccounts({ + filters: [ + { + id: aSearchTerm, + type: 'SEARCH_QUERY' + } + ] + }) + .pipe( + catchError(() => { + return EMPTY; + }), + map(({ accounts }) => { + return accounts.map(({ id, name }) => { + return { + id, + name, + routerLink: internalRoutes.accounts.routerLink, + mode: SearchMode.ACCOUNT as const + }; + }); + }), + takeUntil(this.unsubscribeSubject) + ); + } + private searchAssetProfiles( aSearchTerm: string ): Observable { diff --git a/libs/ui/src/lib/assistant/assistant.html b/libs/ui/src/lib/assistant/assistant.html index f957d9dcc..5954ce369 100644 --- a/libs/ui/src/lib/assistant/assistant.html +++ b/libs/ui/src/lib/assistant/assistant.html @@ -39,9 +39,11 @@ @if (searchFormControl.value) {
@if ( + !isLoading.accounts && !isLoading.assetProfiles && !isLoading.holdings && !isLoading.quickLinks && + searchResults.accounts?.length === 0 && searchResults.assetProfiles?.length === 0 && searchResults.holdings?.length === 0 && searchResults.quickLinks?.length === 0 @@ -76,6 +78,32 @@ }
} + @if (isLoading.accounts || searchResults?.accounts?.length !== 0) { +
+
+ Accounts +
+ @for ( + searchResultItem of searchResults.accounts; + track searchResultItem + ) { + + } + @if (isLoading.accounts) { + + } +
+ } @if (isLoading.holdings || searchResults?.holdings?.length !== 0) {
diff --git a/libs/ui/src/lib/assistant/enums/search-mode.ts b/libs/ui/src/lib/assistant/enums/search-mode.ts index f85ad47e9..af9c4dd45 100644 --- a/libs/ui/src/lib/assistant/enums/search-mode.ts +++ b/libs/ui/src/lib/assistant/enums/search-mode.ts @@ -1,4 +1,5 @@ export enum SearchMode { + ACCOUNT = 'account', ASSET_PROFILE = 'assetProfile', HOLDING = 'holding', QUICK_LINK = 'quickLink' diff --git a/libs/ui/src/lib/assistant/interfaces/interfaces.ts b/libs/ui/src/lib/assistant/interfaces/interfaces.ts index ce8b644be..247641094 100644 --- a/libs/ui/src/lib/assistant/interfaces/interfaces.ts +++ b/libs/ui/src/lib/assistant/interfaces/interfaces.ts @@ -1,8 +1,14 @@ import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; -import { DateRange } from '@ghostfolio/common/types'; +import { AccountWithValue, DateRange } from '@ghostfolio/common/types'; import { SearchMode } from '../enums/search-mode'; +export interface IAccountSearchResultItem + extends Pick { + mode: SearchMode.ACCOUNT; + routerLink: string[]; +} + export interface IAssetSearchResultItem extends AssetProfileIdentifier { assetSubClassString: string; currency: string; @@ -22,10 +28,12 @@ export interface IQuickLinkSearchResultItem { } export type ISearchResultItem = + | IAccountSearchResultItem | IAssetSearchResultItem | IQuickLinkSearchResultItem; export interface ISearchResults { + accounts: ISearchResultItem[]; assetProfiles: ISearchResultItem[]; holdings: ISearchResultItem[]; quickLinks: ISearchResultItem[];