Feature/extend assistant by asset class selector (#2957)
* Remove tabs * Add asset class selector * Update changelog
This commit is contained in:
parent
c68d113d27
commit
06ba7a4b1b
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Extended the assistant by an asset class selector (experimental)
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved the usability of the account selector in the assistant (experimental)
|
||||
|
@ -42,6 +42,10 @@ export class UpdateUserSettingDto {
|
||||
@IsOptional()
|
||||
'filters.accounts'?: string[];
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
'filters.assetClasses'?: string[];
|
||||
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
'filters.tags'?: string[];
|
||||
|
@ -170,6 +170,8 @@ export class HeaderComponent implements OnChanges {
|
||||
|
||||
if (filter.type === 'ACCOUNT') {
|
||||
filtersType = 'accounts';
|
||||
} else if (filter.type === 'ASSET_CLASS') {
|
||||
filtersType = 'assetClasses';
|
||||
} else if (filter.type === 'TAG') {
|
||||
filtersType = 'tags';
|
||||
}
|
||||
|
@ -58,6 +58,13 @@ export class UserService extends ObservableStore<UserStoreState> {
|
||||
});
|
||||
}
|
||||
|
||||
if (user.settings['filters.assetClasses']) {
|
||||
filters.push({
|
||||
id: user.settings['filters.assetClasses'][0],
|
||||
type: 'ASSET_CLASS'
|
||||
});
|
||||
}
|
||||
|
||||
if (user.settings['filters.tags']) {
|
||||
filters.push({
|
||||
id: user.settings['filters.tags'][0],
|
||||
|
@ -22,7 +22,7 @@ import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { Filter, User } from '@ghostfolio/common/interfaces';
|
||||
import { DateRange } from '@ghostfolio/common/types';
|
||||
import { translate } from '@ghostfolio/ui/i18n';
|
||||
import { Account, Tag } from '@prisma/client';
|
||||
import { Account, AssetClass, Tag } from '@prisma/client';
|
||||
import { EMPTY, Observable, Subject, lastValueFrom } from 'rxjs';
|
||||
import {
|
||||
catchError,
|
||||
@ -92,6 +92,7 @@ export class AssistantComponent implements OnChanges, OnDestroy, OnInit {
|
||||
public static readonly SEARCH_RESULTS_DEFAULT_LIMIT = 5;
|
||||
|
||||
public accounts: Account[] = [];
|
||||
public assetClasses: Filter[] = [];
|
||||
public dateRangeFormControl = new FormControl<string>(undefined);
|
||||
public readonly dateRangeOptions = [
|
||||
{ label: $localize`Today`, value: '1d' },
|
||||
@ -116,6 +117,7 @@ export class AssistantComponent implements OnChanges, OnDestroy, OnInit {
|
||||
];
|
||||
public filterForm = this.formBuilder.group({
|
||||
account: new FormControl<string>(undefined),
|
||||
assetClass: new FormControl<string>(undefined),
|
||||
tag: new FormControl<string>(undefined)
|
||||
});
|
||||
public isLoading = false;
|
||||
@ -126,8 +128,9 @@ export class AssistantComponent implements OnChanges, OnDestroy, OnInit {
|
||||
assetProfiles: [],
|
||||
holdings: []
|
||||
};
|
||||
public tags: Tag[] = [];
|
||||
public tags: Filter[] = [];
|
||||
|
||||
private filterTypes: Filter['type'][] = ['ACCOUNT', 'ASSET_CLASS', 'TAG'];
|
||||
private keyManager: FocusKeyManager<AssistantListItemComponent>;
|
||||
private unsubscribeSubject = new Subject<void>();
|
||||
|
||||
@ -140,28 +143,38 @@ export class AssistantComponent implements OnChanges, OnDestroy, OnInit {
|
||||
|
||||
public ngOnInit() {
|
||||
this.accounts = this.user?.accounts;
|
||||
this.assetClasses = Object.keys(AssetClass).map((assetClass) => {
|
||||
return {
|
||||
id: assetClass,
|
||||
label: translate(assetClass),
|
||||
type: 'ASSET_CLASS'
|
||||
};
|
||||
});
|
||||
this.tags = this.user?.tags.map(({ id, name }) => {
|
||||
return {
|
||||
id,
|
||||
name: translate(name)
|
||||
label: translate(name),
|
||||
type: 'TAG'
|
||||
};
|
||||
});
|
||||
|
||||
this.filterForm.valueChanges
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ account, tag }) => {
|
||||
.subscribe(({ account, assetClass, tag }) => {
|
||||
this.filtersChanged.emit([
|
||||
{
|
||||
id: account,
|
||||
type: 'ACCOUNT'
|
||||
},
|
||||
{
|
||||
id: assetClass,
|
||||
type: 'ASSET_CLASS'
|
||||
},
|
||||
{
|
||||
id: tag,
|
||||
type: 'TAG'
|
||||
}
|
||||
]);
|
||||
|
||||
this.onCloseAssistant();
|
||||
});
|
||||
|
||||
this.searchFormControl.valueChanges
|
||||
@ -209,6 +222,7 @@ export class AssistantComponent implements OnChanges, OnDestroy, OnInit {
|
||||
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
|
||||
},
|
||||
{
|
||||
@ -257,16 +271,14 @@ export class AssistantComponent implements OnChanges, OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
public onResetFilters() {
|
||||
this.filtersChanged.emit([
|
||||
{
|
||||
id: null,
|
||||
type: 'ACCOUNT'
|
||||
},
|
||||
{
|
||||
id: null,
|
||||
type: 'TAG'
|
||||
}
|
||||
]);
|
||||
this.filtersChanged.emit(
|
||||
this.filterTypes.map((type) => {
|
||||
return {
|
||||
type,
|
||||
id: null
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
this.onCloseAssistant();
|
||||
}
|
||||
|
@ -89,9 +89,9 @@
|
||||
<form [formGroup]="filterForm">
|
||||
<div
|
||||
*ngIf="!(isLoading || searchFormControl.value) && user?.settings?.isExperimentalFeatures"
|
||||
class="filter-container"
|
||||
class="filter-container p-3"
|
||||
>
|
||||
<div class="p-3">
|
||||
<div class="mb-3">
|
||||
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||
<mat-label i18n>Date Range</mat-label>
|
||||
<mat-select
|
||||
@ -104,60 +104,51 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<mat-tab-group
|
||||
animationDuration="0"
|
||||
mat-align-tabs="start"
|
||||
[mat-stretch-tabs]="false"
|
||||
>
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label
|
||||
><ion-icon name="albums-outline" /><span
|
||||
class="d-none d-sm-block ml-2"
|
||||
i18n
|
||||
>Accounts</span
|
||||
></ng-template
|
||||
>
|
||||
<div class="p-3">
|
||||
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||
<mat-select formControlName="account">
|
||||
<mat-option [value]="null"></mat-option>
|
||||
@for (account of accounts; track account.id) {
|
||||
<mat-option [value]="account.id">
|
||||
<div class="d-flex">
|
||||
<gf-symbol-icon
|
||||
*ngIf="account.Platform?.url"
|
||||
class="mr-1"
|
||||
[tooltip]="account.Platform?.name"
|
||||
[url]="account.Platform?.url"
|
||||
/><span>{{ account.name }}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label
|
||||
><ion-icon name="pricetag-outline" /><span
|
||||
class="d-none d-sm-block ml-2"
|
||||
i18n
|
||||
>Tags</span
|
||||
></ng-template
|
||||
>
|
||||
<div class="p-3">
|
||||
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||
<mat-select formControlName="tag">
|
||||
<mat-option [value]="null"></mat-option>
|
||||
@for (tag of tags; track tag.id) {
|
||||
<mat-option [value]="tag.id">{{ tag.name }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
<div class="pb-3 px-3">
|
||||
<div class="mb-3">
|
||||
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||
<mat-label i18n>Accounts</mat-label>
|
||||
<mat-select formControlName="account">
|
||||
<mat-option [value]="null"></mat-option>
|
||||
@for (account of accounts; track account.id) {
|
||||
<mat-option [value]="account.id">
|
||||
<div class="d-flex">
|
||||
<gf-symbol-icon
|
||||
*ngIf="account.Platform?.url"
|
||||
class="mr-1"
|
||||
[tooltip]="account.Platform?.name"
|
||||
[url]="account.Platform?.url"
|
||||
/><span>{{ account.name }}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||
<mat-label i18n>Tags</mat-label>
|
||||
<mat-select formControlName="tag">
|
||||
<mat-option [value]="null"></mat-option>
|
||||
@for (tag of tags; track tag.id) {
|
||||
<mat-option [value]="tag.id">{{ tag.label }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||
<mat-label i18n>Asset Classes</mat-label>
|
||||
<mat-select formControlName="assetClass">
|
||||
<mat-option [value]="null"></mat-option>
|
||||
@for (assetClass of assetClasses; track assetClass.id) {
|
||||
<mat-option [value]="assetClass.id"
|
||||
>{{ assetClass.label }}</mat-option
|
||||
>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="w-100"
|
||||
color="primary"
|
||||
|
@ -4,7 +4,6 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { GfSymbolIconModule } from '@ghostfolio/client/components/symbol-icon/symbol-icon.module';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
@ -23,7 +22,6 @@ import { AssistantComponent } from './assistant.component';
|
||||
MatButtonModule,
|
||||
MatFormFieldModule,
|
||||
MatSelectModule,
|
||||
MatTabsModule,
|
||||
NgxSkeletonLoaderModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule
|
||||
|
Loading…
x
Reference in New Issue
Block a user