Feature/extend assistant by holding selector (#4031)
* Extend assistant by holding selector * Update changelog
This commit is contained in:
parent
9f72835d58
commit
6057794eb6
@ -5,6 +5,12 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Extended the assistant by a holding selector
|
||||||
|
|
||||||
## 2.122.0 - 2024-11-07
|
## 2.122.0 - 2024-11-07
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -74,12 +74,15 @@ export class PortfolioController {
|
|||||||
@Get('details')
|
@Get('details')
|
||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
@UseInterceptors(RedactValuesInResponseInterceptor)
|
@UseInterceptors(RedactValuesInResponseInterceptor)
|
||||||
|
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
||||||
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
||||||
public async getDetails(
|
public async getDetails(
|
||||||
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
|
||||||
@Query('accounts') filterByAccounts?: string,
|
@Query('accounts') filterByAccounts?: string,
|
||||||
@Query('assetClasses') filterByAssetClasses?: string,
|
@Query('assetClasses') filterByAssetClasses?: string,
|
||||||
|
@Query('dataSource') filterByDataSource?: string,
|
||||||
@Query('range') dateRange: DateRange = 'max',
|
@Query('range') dateRange: DateRange = 'max',
|
||||||
|
@Query('symbol') filterBySymbol?: string,
|
||||||
@Query('tags') filterByTags?: string,
|
@Query('tags') filterByTags?: string,
|
||||||
@Query('withMarkets') withMarketsParam = 'false'
|
@Query('withMarkets') withMarketsParam = 'false'
|
||||||
): Promise<PortfolioDetails & { hasError: boolean }> {
|
): Promise<PortfolioDetails & { hasError: boolean }> {
|
||||||
@ -95,6 +98,8 @@ export class PortfolioController {
|
|||||||
const filters = this.apiService.buildFiltersFromQueryParams({
|
const filters = this.apiService.buildFiltersFromQueryParams({
|
||||||
filterByAccounts,
|
filterByAccounts,
|
||||||
filterByAssetClasses,
|
filterByAssetClasses,
|
||||||
|
filterByDataSource,
|
||||||
|
filterBySymbol,
|
||||||
filterByTags
|
filterByTags
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -289,17 +294,22 @@ export class PortfolioController {
|
|||||||
|
|
||||||
@Get('dividends')
|
@Get('dividends')
|
||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
|
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
||||||
public async getDividends(
|
public async getDividends(
|
||||||
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
|
||||||
@Query('accounts') filterByAccounts?: string,
|
@Query('accounts') filterByAccounts?: string,
|
||||||
@Query('assetClasses') filterByAssetClasses?: string,
|
@Query('assetClasses') filterByAssetClasses?: string,
|
||||||
|
@Query('dataSource') filterByDataSource?: string,
|
||||||
@Query('groupBy') groupBy?: GroupBy,
|
@Query('groupBy') groupBy?: GroupBy,
|
||||||
@Query('range') dateRange: DateRange = 'max',
|
@Query('range') dateRange: DateRange = 'max',
|
||||||
|
@Query('symbol') filterBySymbol?: string,
|
||||||
@Query('tags') filterByTags?: string
|
@Query('tags') filterByTags?: string
|
||||||
): Promise<PortfolioDividends> {
|
): Promise<PortfolioDividends> {
|
||||||
const filters = this.apiService.buildFiltersFromQueryParams({
|
const filters = this.apiService.buildFiltersFromQueryParams({
|
||||||
filterByAccounts,
|
filterByAccounts,
|
||||||
filterByAssetClasses,
|
filterByAssetClasses,
|
||||||
|
filterByDataSource,
|
||||||
|
filterBySymbol,
|
||||||
filterByTags
|
filterByTags
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -356,21 +366,26 @@ export class PortfolioController {
|
|||||||
@Get('holdings')
|
@Get('holdings')
|
||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
@UseInterceptors(RedactValuesInResponseInterceptor)
|
@UseInterceptors(RedactValuesInResponseInterceptor)
|
||||||
|
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
||||||
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
||||||
public async getHoldings(
|
public async getHoldings(
|
||||||
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
|
||||||
@Query('accounts') filterByAccounts?: string,
|
@Query('accounts') filterByAccounts?: string,
|
||||||
@Query('assetClasses') filterByAssetClasses?: string,
|
@Query('assetClasses') filterByAssetClasses?: string,
|
||||||
|
@Query('dataSource') filterByDataSource?: string,
|
||||||
@Query('holdingType') filterByHoldingType?: string,
|
@Query('holdingType') filterByHoldingType?: string,
|
||||||
@Query('query') filterBySearchQuery?: string,
|
@Query('query') filterBySearchQuery?: string,
|
||||||
@Query('range') dateRange: DateRange = 'max',
|
@Query('range') dateRange: DateRange = 'max',
|
||||||
|
@Query('symbol') filterBySymbol?: string,
|
||||||
@Query('tags') filterByTags?: string
|
@Query('tags') filterByTags?: string
|
||||||
): Promise<PortfolioHoldingsResponse> {
|
): Promise<PortfolioHoldingsResponse> {
|
||||||
const filters = this.apiService.buildFiltersFromQueryParams({
|
const filters = this.apiService.buildFiltersFromQueryParams({
|
||||||
filterByAccounts,
|
filterByAccounts,
|
||||||
filterByAssetClasses,
|
filterByAssetClasses,
|
||||||
|
filterByDataSource,
|
||||||
filterByHoldingType,
|
filterByHoldingType,
|
||||||
filterBySearchQuery,
|
filterBySearchQuery,
|
||||||
|
filterBySymbol,
|
||||||
filterByTags
|
filterByTags
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -386,17 +401,22 @@ export class PortfolioController {
|
|||||||
|
|
||||||
@Get('investments')
|
@Get('investments')
|
||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
|
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
||||||
public async getInvestments(
|
public async getInvestments(
|
||||||
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
|
||||||
@Query('accounts') filterByAccounts?: string,
|
@Query('accounts') filterByAccounts?: string,
|
||||||
@Query('assetClasses') filterByAssetClasses?: string,
|
@Query('assetClasses') filterByAssetClasses?: string,
|
||||||
|
@Query('dataSource') filterByDataSource?: string,
|
||||||
@Query('groupBy') groupBy?: GroupBy,
|
@Query('groupBy') groupBy?: GroupBy,
|
||||||
@Query('range') dateRange: DateRange = 'max',
|
@Query('range') dateRange: DateRange = 'max',
|
||||||
|
@Query('symbol') filterBySymbol?: string,
|
||||||
@Query('tags') filterByTags?: string
|
@Query('tags') filterByTags?: string
|
||||||
): Promise<PortfolioInvestments> {
|
): Promise<PortfolioInvestments> {
|
||||||
const filters = this.apiService.buildFiltersFromQueryParams({
|
const filters = this.apiService.buildFiltersFromQueryParams({
|
||||||
filterByAccounts,
|
filterByAccounts,
|
||||||
filterByAssetClasses,
|
filterByAssetClasses,
|
||||||
|
filterByDataSource,
|
||||||
|
filterBySymbol,
|
||||||
filterByTags
|
filterByTags
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -451,13 +471,16 @@ export class PortfolioController {
|
|||||||
@Get('performance')
|
@Get('performance')
|
||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
@UseInterceptors(PerformanceLoggingInterceptor)
|
@UseInterceptors(PerformanceLoggingInterceptor)
|
||||||
|
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
||||||
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
||||||
@Version('2')
|
@Version('2')
|
||||||
public async getPerformanceV2(
|
public async getPerformanceV2(
|
||||||
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
|
||||||
@Query('accounts') filterByAccounts?: string,
|
@Query('accounts') filterByAccounts?: string,
|
||||||
@Query('assetClasses') filterByAssetClasses?: string,
|
@Query('assetClasses') filterByAssetClasses?: string,
|
||||||
|
@Query('dataSource') filterByDataSource?: string,
|
||||||
@Query('range') dateRange: DateRange = 'max',
|
@Query('range') dateRange: DateRange = 'max',
|
||||||
|
@Query('symbol') filterBySymbol?: string,
|
||||||
@Query('tags') filterByTags?: string,
|
@Query('tags') filterByTags?: string,
|
||||||
@Query('withExcludedAccounts') withExcludedAccountsParam = 'false'
|
@Query('withExcludedAccounts') withExcludedAccountsParam = 'false'
|
||||||
): Promise<PortfolioPerformanceResponse> {
|
): Promise<PortfolioPerformanceResponse> {
|
||||||
@ -466,6 +489,8 @@ export class PortfolioController {
|
|||||||
const filters = this.apiService.buildFiltersFromQueryParams({
|
const filters = this.apiService.buildFiltersFromQueryParams({
|
||||||
filterByAccounts,
|
filterByAccounts,
|
||||||
filterByAssetClasses,
|
filterByAssetClasses,
|
||||||
|
filterByDataSource,
|
||||||
|
filterBySymbol,
|
||||||
filterByTags
|
filterByTags
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -64,6 +64,14 @@ export class UpdateUserSettingDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
'filters.assetClasses'?: string[];
|
'filters.assetClasses'?: string[];
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
'filters.dataSource'?: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
'filters.symbol'?: string;
|
||||||
|
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
'filters.tags'?: string[];
|
'filters.tags'?: string[];
|
||||||
|
@ -175,17 +175,17 @@ export class HeaderComponent implements OnChanges {
|
|||||||
const userSetting: UpdateUserSettingDto = {};
|
const userSetting: UpdateUserSettingDto = {};
|
||||||
|
|
||||||
for (const filter of filters) {
|
for (const filter of filters) {
|
||||||
let filtersType: string;
|
|
||||||
|
|
||||||
if (filter.type === 'ACCOUNT') {
|
if (filter.type === 'ACCOUNT') {
|
||||||
filtersType = 'accounts';
|
userSetting['filters.accounts'] = filter.id ? [filter.id] : null;
|
||||||
} else if (filter.type === 'ASSET_CLASS') {
|
} else if (filter.type === 'ASSET_CLASS') {
|
||||||
filtersType = 'assetClasses';
|
userSetting['filters.assetClasses'] = filter.id ? [filter.id] : null;
|
||||||
|
} else if (filter.type === 'DATA_SOURCE') {
|
||||||
|
userSetting['filters.dataSource'] = filter.id ? filter.id : null;
|
||||||
|
} else if (filter.type === 'SYMBOL') {
|
||||||
|
userSetting['filters.symbol'] = filter.id ? filter.id : null;
|
||||||
} else if (filter.type === 'TAG') {
|
} else if (filter.type === 'TAG') {
|
||||||
filtersType = 'tags';
|
userSetting['filters.tags'] = filter.id ? [filter.id] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
userSetting[`filters.${filtersType}`] = filter.id ? [filter.id] : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
|
@ -532,7 +532,7 @@ export class DataService {
|
|||||||
}: {
|
}: {
|
||||||
filters?: Filter[];
|
filters?: Filter[];
|
||||||
range?: DateRange;
|
range?: DateRange;
|
||||||
}) {
|
} = {}) {
|
||||||
let params = this.buildFiltersAsQueryParams({ filters });
|
let params = this.buildFiltersAsQueryParams({ filters });
|
||||||
|
|
||||||
if (range) {
|
if (range) {
|
||||||
|
@ -65,6 +65,20 @@ export class UserService extends ObservableStore<UserStoreState> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user?.settings['filters.dataSource']) {
|
||||||
|
filters.push({
|
||||||
|
id: user.settings['filters.dataSource'],
|
||||||
|
type: 'DATA_SOURCE'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user?.settings['filters.symbol']) {
|
||||||
|
filters.push({
|
||||||
|
id: user.settings['filters.symbol'],
|
||||||
|
type: 'SYMBOL'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (user?.settings['filters.tags']) {
|
if (user?.settings['filters.tags']) {
|
||||||
filters.push({
|
filters.push({
|
||||||
id: user.settings['filters.tags'][0],
|
id: user.settings['filters.tags'][0],
|
||||||
|
@ -14,6 +14,8 @@ export interface UserSettings {
|
|||||||
dateRange?: DateRange;
|
dateRange?: DateRange;
|
||||||
emergencyFund?: number;
|
emergencyFund?: number;
|
||||||
'filters.accounts'?: string[];
|
'filters.accounts'?: string[];
|
||||||
|
'filters.dataSource'?: string;
|
||||||
|
'filters.symbol'?: string;
|
||||||
'filters.tags'?: string[];
|
'filters.tags'?: string[];
|
||||||
holdingsViewMode?: HoldingsViewMode;
|
holdingsViewMode?: HoldingsViewMode;
|
||||||
isExperimentalFeatures?: boolean;
|
isExperimentalFeatures?: boolean;
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset-profile-icon/asset-profile-icon.component';
|
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 { AdminService } from '@ghostfolio/client/services/admin.service';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { Filter, User } from '@ghostfolio/common/interfaces';
|
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
|
||||||
|
import { Filter, PortfolioPosition, User } from '@ghostfolio/common/interfaces';
|
||||||
import { DateRange } from '@ghostfolio/common/types';
|
import { DateRange } from '@ghostfolio/common/types';
|
||||||
import { translate } from '@ghostfolio/ui/i18n';
|
import { translate } from '@ghostfolio/ui/i18n';
|
||||||
|
|
||||||
@ -35,7 +37,7 @@ import { MatFormFieldModule } from '@angular/material/form-field';
|
|||||||
import { MatMenuTrigger } from '@angular/material/menu';
|
import { MatMenuTrigger } from '@angular/material/menu';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { Account, AssetClass } from '@prisma/client';
|
import { Account, AssetClass, DataSource } from '@prisma/client';
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
import { EMPTY, Observable, Subject, lastValueFrom } from 'rxjs';
|
import { EMPTY, Observable, Subject, lastValueFrom } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
@ -61,6 +63,7 @@ import {
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
GfAssetProfileIconComponent,
|
GfAssetProfileIconComponent,
|
||||||
GfAssistantListItemComponent,
|
GfAssistantListItemComponent,
|
||||||
|
GfSymbolModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
@ -132,8 +135,10 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
public filterForm = this.formBuilder.group({
|
public filterForm = this.formBuilder.group({
|
||||||
account: new FormControl<string>(undefined),
|
account: new FormControl<string>(undefined),
|
||||||
assetClass: new FormControl<string>(undefined),
|
assetClass: new FormControl<string>(undefined),
|
||||||
|
holding: new FormControl<PortfolioPosition>(undefined),
|
||||||
tag: new FormControl<string>(undefined)
|
tag: new FormControl<string>(undefined)
|
||||||
});
|
});
|
||||||
|
public holdings: PortfolioPosition[] = [];
|
||||||
public isLoading = false;
|
public isLoading = false;
|
||||||
public isOpen = false;
|
public isOpen = false;
|
||||||
public placeholder = $localize`Find holding...`;
|
public placeholder = $localize`Find holding...`;
|
||||||
@ -144,7 +149,13 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
};
|
};
|
||||||
public tags: Filter[] = [];
|
public tags: Filter[] = [];
|
||||||
|
|
||||||
private filterTypes: Filter['type'][] = ['ACCOUNT', 'ASSET_CLASS', 'TAG'];
|
private filterTypes: Filter['type'][] = [
|
||||||
|
'ACCOUNT',
|
||||||
|
'ASSET_CLASS',
|
||||||
|
'DATA_SOURCE',
|
||||||
|
'SYMBOL',
|
||||||
|
'TAG'
|
||||||
|
];
|
||||||
private keyManager: FocusKeyManager<GfAssistantListItemComponent>;
|
private keyManager: FocusKeyManager<GfAssistantListItemComponent>;
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
@ -156,6 +167,8 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
|
this.initializeFilterForm();
|
||||||
|
|
||||||
this.assetClasses = Object.keys(AssetClass).map((assetClass) => {
|
this.assetClasses = Object.keys(AssetClass).map((assetClass) => {
|
||||||
return {
|
return {
|
||||||
id: assetClass,
|
id: assetClass,
|
||||||
@ -263,16 +276,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
this.filterForm.enable({ emitEvent: false });
|
this.filterForm.enable({ emitEvent: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.filterForm.setValue(
|
this.initializeFilterForm();
|
||||||
{
|
|
||||||
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.tags =
|
this.tags =
|
||||||
this.user?.tags
|
this.user?.tags
|
||||||
@ -298,6 +302,19 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public holdingComparisonFunction(
|
||||||
|
option: PortfolioPosition,
|
||||||
|
value: PortfolioPosition
|
||||||
|
): boolean {
|
||||||
|
if (value === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
getAssetProfileIdentifier(option) === getAssetProfileIdentifier(value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public async initialize() {
|
public async initialize() {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.keyManager = new FocusKeyManager(this.assistantListItems).withWrap();
|
this.keyManager = new FocusKeyManager(this.assistantListItems).withWrap();
|
||||||
@ -331,6 +348,14 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
id: this.filterForm.get('assetClass').value,
|
id: this.filterForm.get('assetClass').value,
|
||||||
type: 'ASSET_CLASS'
|
type: 'ASSET_CLASS'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: this.filterForm.get('holding').value?.dataSource,
|
||||||
|
type: 'DATA_SOURCE'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: this.filterForm.get('holding').value?.symbol,
|
||||||
|
type: 'SYMBOL'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: this.filterForm.get('tag').value,
|
id: this.filterForm.get('tag').value,
|
||||||
type: 'TAG'
|
type: 'TAG'
|
||||||
@ -473,4 +498,47 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
takeUntil(this.unsubscribeSubject)
|
takeUntil(this.unsubscribeSubject)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private initializeFilterForm() {
|
||||||
|
this.dataService
|
||||||
|
.fetchPortfolioHoldings()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(({ holdings }) => {
|
||||||
|
this.holdings = holdings
|
||||||
|
.filter(({ assetSubClass }) => {
|
||||||
|
return !['CASH'].includes(assetSubClass);
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
return a.name?.localeCompare(b.name);
|
||||||
|
});
|
||||||
|
this.setFilterFormValues();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private setFilterFormValues() {
|
||||||
|
const dataSource = this.user?.settings?.[
|
||||||
|
'filters.dataSource'
|
||||||
|
] as DataSource;
|
||||||
|
const symbol = this.user?.settings?.['filters.symbol'];
|
||||||
|
const selectedHolding = this.holdings.find((holding) => {
|
||||||
|
return (
|
||||||
|
getAssetProfileIdentifier({
|
||||||
|
dataSource: holding.dataSource,
|
||||||
|
symbol: holding.symbol
|
||||||
|
}) === getAssetProfileIdentifier({ dataSource, symbol })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filterForm.setValue(
|
||||||
|
{
|
||||||
|
account: this.user?.settings?.['filters.accounts']?.[0] ?? null,
|
||||||
|
assetClass: this.user?.settings?.['filters.assetClasses']?.[0] ?? null,
|
||||||
|
holding: selectedHolding ?? null,
|
||||||
|
tag: this.user?.settings?.['filters.tags']?.[0] ?? null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emitEvent: false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,6 +122,34 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||||
|
<mat-label i18n>Holding</mat-label>
|
||||||
|
<mat-select
|
||||||
|
formControlName="holding"
|
||||||
|
[compareWith]="holdingComparisonFunction"
|
||||||
|
>
|
||||||
|
<mat-select-trigger>{{
|
||||||
|
filterForm.get('holding')?.value?.name
|
||||||
|
}}</mat-select-trigger>
|
||||||
|
<mat-option [value]="null" />
|
||||||
|
@for (holding of holdings; track holding.name) {
|
||||||
|
<mat-option [value]="holding">
|
||||||
|
<div class="line-height-1 text-truncate">
|
||||||
|
<span
|
||||||
|
><b>{{ holding.name }}</b></span
|
||||||
|
>
|
||||||
|
<br />
|
||||||
|
<small class="text-muted"
|
||||||
|
>{{ holding.symbol | gfSymbol }} ·
|
||||||
|
{{ holding.currency }}</small
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<mat-form-field appearance="outline" class="w-100 without-hint">
|
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||||
<mat-label i18n>Tags</mat-label>
|
<mat-label i18n>Tags</mat-label>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user