From 73de220576b14ab5e49fc1745570786781db0398 Mon Sep 17 00:00:00 2001 From: Amandee Ellawala Date: Mon, 4 Nov 2024 16:28:37 +0000 Subject: [PATCH] Implement client side of Feature/Extend assistant by selector for holdings #3941 --- .../src/app/user/update-user-setting.dto.ts | 8 ++ .../app/components/header/header.component.ts | 4 + .../src/app/services/user/user.service.ts | 14 +++ .../lib/interfaces/user-settings.interface.ts | 2 + .../src/lib/assistant/assistant.component.ts | 86 +++++++++++++++---- libs/ui/src/lib/assistant/assistant.html | 26 ++++++ 6 files changed, 125 insertions(+), 15 deletions(-) diff --git a/apps/api/src/app/user/update-user-setting.dto.ts b/apps/api/src/app/user/update-user-setting.dto.ts index 13a3a5d2c..d9047de72 100644 --- a/apps/api/src/app/user/update-user-setting.dto.ts +++ b/apps/api/src/app/user/update-user-setting.dto.ts @@ -68,6 +68,14 @@ export class UpdateUserSettingDto { @IsOptional() 'filters.tags'?: string[]; + @IsArray() + @IsOptional() + 'filters.dataSource'?: string[]; + + @IsArray() + @IsOptional() + 'filters.symbol'?: string[]; + @IsIn(['CHART', 'TABLE'] as HoldingsViewMode[]) @IsOptional() holdingsViewMode?: HoldingsViewMode; diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index 1739d113f..6ee27be68 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -183,6 +183,10 @@ export class HeaderComponent implements OnChanges { filtersType = 'assetClasses'; } else if (filter.type === 'TAG') { filtersType = 'tags'; + } else if (filter.type === 'DATA_SOURCE') { + filtersType = 'dataSource'; + } else if (filter.type === 'SYMBOL') { + filtersType = 'symbol'; } userSetting[`filters.${filtersType}`] = filter.id ? [filter.id] : null; diff --git a/apps/client/src/app/services/user/user.service.ts b/apps/client/src/app/services/user/user.service.ts index 3ecc58c16..3f63841a6 100644 --- a/apps/client/src/app/services/user/user.service.ts +++ b/apps/client/src/app/services/user/user.service.ts @@ -72,6 +72,20 @@ export class UserService extends ObservableStore { }); } + if (user?.settings['filters.dataSource']) { + filters.push({ + id: user.settings['filters.dataSource'][0], + type: 'DATA_SOURCE' + }); + } + + if (user?.settings['filters.symbol']) { + filters.push({ + id: user.settings['filters.symbol'][0], + type: 'SYMBOL' + }); + } + return filters; } diff --git a/libs/common/src/lib/interfaces/user-settings.interface.ts b/libs/common/src/lib/interfaces/user-settings.interface.ts index e9e90e71f..01e3a605f 100644 --- a/libs/common/src/lib/interfaces/user-settings.interface.ts +++ b/libs/common/src/lib/interfaces/user-settings.interface.ts @@ -15,6 +15,8 @@ export interface UserSettings { emergencyFund?: number; 'filters.accounts'?: string[]; 'filters.tags'?: string[]; + 'filters.dataSource'?: string[]; + 'filters.symbol'?: string[]; holdingsViewMode?: HoldingsViewMode; isExperimentalFeatures?: boolean; isRestrictedView?: boolean; diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index d73cdb416..520eb1087 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -1,7 +1,8 @@ import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset-profile-icon/asset-profile-icon.component'; +import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; -import { Filter, User } from '@ghostfolio/common/interfaces'; +import { Filter, PortfolioPosition, User } from '@ghostfolio/common/interfaces'; import { DateRange } from '@ghostfolio/common/types'; import { translate } from '@ghostfolio/ui/i18n'; @@ -36,6 +37,7 @@ import { MatMenuTrigger } from '@angular/material/menu'; import { MatSelectModule } from '@angular/material/select'; import { RouterModule } from '@angular/router'; import { Account, AssetClass } from '@prisma/client'; +import { sortBy } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { EMPTY, Observable, Subject, lastValueFrom } from 'rxjs'; import { @@ -66,7 +68,8 @@ import { MatSelectModule, NgxSkeletonLoaderModule, ReactiveFormsModule, - RouterModule + RouterModule, + GfSymbolModule ], schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-assistant', @@ -132,7 +135,8 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { public filterForm = this.formBuilder.group({ account: new FormControl(undefined), assetClass: new FormControl(undefined), - tag: new FormControl(undefined) + tag: new FormControl(undefined), + holdings: new FormControl(undefined) }); public isLoading = false; public isOpen = false; @@ -143,8 +147,15 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { holdings: [] }; public tags: Filter[] = []; - - private filterTypes: Filter['type'][] = ['ACCOUNT', 'ASSET_CLASS', 'TAG']; + public allPortfolioHoldings: PortfolioPosition[] = []; + + private filterTypes: Filter['type'][] = [ + 'ACCOUNT', + 'ASSET_CLASS', + 'TAG', + 'DATA_SOURCE', + 'SYMBOL' + ]; private keyManager: FocusKeyManager; private unsubscribeSubject = new Subject(); @@ -156,6 +167,8 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { ) {} public ngOnInit() { + this.loadPortfolioHoldings(); + this.assetClasses = Object.keys(AssetClass).map((assetClass) => { return { id: assetClass, @@ -263,16 +276,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { this.filterForm.enable({ emitEvent: false }); } - this.filterForm.setValue( - { - account: this.user?.settings?.['filters.accounts']?.[0] ?? null, - assetClass: this.user?.settings?.['filters.assetClasses']?.[0] ?? null, - tag: this.user?.settings?.['filters.tags']?.[0] ?? null - }, - { - emitEvent: false - } - ); + this.loadPortfolioHoldings(); this.tags = this.user?.tags @@ -334,6 +338,14 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { { id: this.filterForm.get('tag').value, type: 'TAG' + }, + { + id: this.filterForm.get('holdings').value.dataSource, + type: 'DATA_SOURCE' + }, + { + id: this.filterForm.get('holdings').value.symbol, + type: 'SYMBOL' } ]); @@ -473,4 +485,48 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { takeUntil(this.unsubscribeSubject) ); } + + public holdingComparisonFunction(option, value): boolean { + return ( + option.dataSource === value.dataSource && option.symbol === value.symbol + ); + } + + private loadPortfolioHoldings() { + this.dataService + .fetchPortfolioHoldings({ + range: 'max' + }) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ holdings }) => { + this.allPortfolioHoldings = sortBy(holdings, ({ name }) => { + return name.toLowerCase(); + }); + this.setFormValues(); + + //this.changeDetectorRef.markForCheck(); + }); + } + + private setFormValues() { + const dataSource = this.user?.settings?.['filters.dataSource']?.[0] ?? null; + const symbol = this.user?.settings?.['filters.symbol']?.[0] ?? null; + const selectedHolding = this.allPortfolioHoldings.filter( + (h) => h.dataSource === dataSource && h.symbol === symbol + ); + + const holding = selectedHolding[0]; + + this.filterForm.setValue( + { + account: this.user?.settings?.['filters.accounts']?.[0] ?? null, + assetClass: this.user?.settings?.['filters.assetClasses']?.[0] ?? null, + tag: this.user?.settings?.['filters.tags']?.[0] ?? null, + holdings: holding + }, + { + emitEvent: false + } + ); + } } diff --git a/libs/ui/src/lib/assistant/assistant.html b/libs/ui/src/lib/assistant/assistant.html index 648c791ab..8c14d8c43 100644 --- a/libs/ui/src/lib/assistant/assistant.html +++ b/libs/ui/src/lib/assistant/assistant.html @@ -122,6 +122,32 @@ +
+ + Holding + + {{ + filterForm.get('holdings')?.value?.name + }} + + @for (holding of allPortfolioHoldings; track holding.name) { + + {{ holding.name }} +
+ {{ holding.symbol | gfSymbol }} ยท + {{ holding.currency }} +
+ } +
+
+
Tags