|
|
|
@ -12,16 +12,15 @@ import { |
|
|
|
ChangeDetectorRef, |
|
|
|
Component, |
|
|
|
ElementRef, |
|
|
|
EventEmitter, |
|
|
|
HostListener, |
|
|
|
Input, |
|
|
|
OnChanges, |
|
|
|
OnDestroy, |
|
|
|
OnInit, |
|
|
|
Output, |
|
|
|
QueryList, |
|
|
|
ViewChild, |
|
|
|
ViewChildren |
|
|
|
ViewChildren, |
|
|
|
output |
|
|
|
} from '@angular/core'; |
|
|
|
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; |
|
|
|
import { MatButtonModule } from '@angular/material/button'; |
|
|
|
@ -86,37 +85,7 @@ import { |
|
|
|
templateUrl: './assistant.html' |
|
|
|
}) |
|
|
|
export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { |
|
|
|
@HostListener('document:keydown', ['$event']) onKeydown( |
|
|
|
event: KeyboardEvent |
|
|
|
) { |
|
|
|
if (!this.isOpen) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { |
|
|
|
for (const item of this.assistantListItems) { |
|
|
|
item.removeFocus(); |
|
|
|
} |
|
|
|
|
|
|
|
this.keyManager.onKeydown(event); |
|
|
|
|
|
|
|
const currentAssistantListItem = this.getCurrentAssistantListItem(); |
|
|
|
|
|
|
|
if (currentAssistantListItem?.linkElement) { |
|
|
|
currentAssistantListItem.linkElement.nativeElement?.scrollIntoView({ |
|
|
|
behavior: 'smooth', |
|
|
|
block: 'center' |
|
|
|
}); |
|
|
|
} |
|
|
|
} else if (event.key === 'Enter') { |
|
|
|
const currentAssistantListItem = this.getCurrentAssistantListItem(); |
|
|
|
|
|
|
|
if (currentAssistantListItem?.linkElement) { |
|
|
|
currentAssistantListItem.linkElement.nativeElement?.click(); |
|
|
|
event.stopPropagation(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
public static readonly SEARCH_RESULTS_DEFAULT_LIMIT = 5; |
|
|
|
|
|
|
|
@Input() deviceType: string; |
|
|
|
@Input() hasPermissionToAccessAdminControl: boolean; |
|
|
|
@ -124,21 +93,16 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { |
|
|
|
@Input() hasPermissionToChangeFilters: boolean; |
|
|
|
@Input() user: User; |
|
|
|
|
|
|
|
@Output() closed = new EventEmitter<void>(); |
|
|
|
@Output() dateRangeChanged = new EventEmitter<DateRange>(); |
|
|
|
@Output() filtersChanged = new EventEmitter<Filter[]>(); |
|
|
|
|
|
|
|
@ViewChild('menuTrigger') menuTriggerElement: MatMenuTrigger; |
|
|
|
@ViewChild('search', { static: true }) searchElement: ElementRef; |
|
|
|
@ViewChild('search', { static: true }) |
|
|
|
searchElement: ElementRef<HTMLInputElement>; |
|
|
|
|
|
|
|
@ViewChildren(GfAssistantListItemComponent) |
|
|
|
assistantListItems: QueryList<GfAssistantListItemComponent>; |
|
|
|
|
|
|
|
public static readonly SEARCH_RESULTS_DEFAULT_LIMIT = 5; |
|
|
|
|
|
|
|
public accounts: AccountWithPlatform[] = []; |
|
|
|
public assetClasses: Filter[] = []; |
|
|
|
public dateRangeFormControl = new FormControl<string>(undefined); |
|
|
|
public dateRangeFormControl = new FormControl<string | null>(null); |
|
|
|
public dateRangeOptions: DateRangeOption[] = []; |
|
|
|
public holdings: PortfolioPosition[] = []; |
|
|
|
public isLoading = { |
|
|
|
@ -166,6 +130,10 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { |
|
|
|
}; |
|
|
|
public tags: Filter[] = []; |
|
|
|
|
|
|
|
protected readonly closed = output<void>(); |
|
|
|
protected readonly dateRangeChanged = output<DateRange>(); |
|
|
|
protected readonly filtersChanged = output<Filter[]>(); |
|
|
|
|
|
|
|
private readonly PRESELECTION_DELAY = 100; |
|
|
|
|
|
|
|
private filterTypes: Filter['type'][] = [ |
|
|
|
@ -188,6 +156,37 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { |
|
|
|
addIcons({ closeCircleOutline, closeOutline, searchOutline }); |
|
|
|
} |
|
|
|
|
|
|
|
@HostListener('document:keydown', ['$event']) |
|
|
|
public onKeydown(event: KeyboardEvent) { |
|
|
|
if (!this.isOpen) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { |
|
|
|
for (const item of this.assistantListItems) { |
|
|
|
item.removeFocus(); |
|
|
|
} |
|
|
|
|
|
|
|
this.keyManager.onKeydown(event); |
|
|
|
|
|
|
|
const currentAssistantListItem = this.getCurrentAssistantListItem(); |
|
|
|
|
|
|
|
if (currentAssistantListItem?.linkElement) { |
|
|
|
currentAssistantListItem.linkElement.nativeElement?.scrollIntoView({ |
|
|
|
behavior: 'smooth', |
|
|
|
block: 'center' |
|
|
|
}); |
|
|
|
} |
|
|
|
} else if (event.key === 'Enter') { |
|
|
|
const currentAssistantListItem = this.getCurrentAssistantListItem(); |
|
|
|
|
|
|
|
if (currentAssistantListItem?.linkElement) { |
|
|
|
currentAssistantListItem.linkElement.nativeElement?.click(); |
|
|
|
event.stopPropagation(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public ngOnInit() { |
|
|
|
this.assetClasses = Object.keys(AssetClass).map((assetClass) => { |
|
|
|
return { |
|
|
|
@ -482,7 +481,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { |
|
|
|
.subscribe(({ holdings }) => { |
|
|
|
this.holdings = holdings |
|
|
|
.filter(({ assetSubClass }) => { |
|
|
|
return !['CASH'].includes(assetSubClass); |
|
|
|
return assetSubClass && !['CASH'].includes(assetSubClass); |
|
|
|
}) |
|
|
|
.sort((a, b) => { |
|
|
|
return a.name?.localeCompare(b.name); |
|
|
|
@ -499,23 +498,23 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { |
|
|
|
|
|
|
|
this.filtersChanged.emit([ |
|
|
|
{ |
|
|
|
id: filterValue?.account, |
|
|
|
id: filterValue?.account ?? '', |
|
|
|
type: 'ACCOUNT' |
|
|
|
}, |
|
|
|
{ |
|
|
|
id: filterValue?.assetClass, |
|
|
|
id: filterValue?.assetClass ?? '', |
|
|
|
type: 'ASSET_CLASS' |
|
|
|
}, |
|
|
|
{ |
|
|
|
id: filterValue?.holding?.dataSource, |
|
|
|
id: filterValue?.holding?.dataSource ?? '', |
|
|
|
type: 'DATA_SOURCE' |
|
|
|
}, |
|
|
|
{ |
|
|
|
id: filterValue?.holding?.symbol, |
|
|
|
id: filterValue?.holding?.symbol ?? '', |
|
|
|
type: 'SYMBOL' |
|
|
|
}, |
|
|
|
{ |
|
|
|
id: filterValue?.tag, |
|
|
|
id: filterValue?.tag ?? '', |
|
|
|
type: 'TAG' |
|
|
|
} |
|
|
|
]); |
|
|
|
@ -541,7 +540,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { |
|
|
|
this.filterTypes.map((type) => { |
|
|
|
return { |
|
|
|
type, |
|
|
|
id: null |
|
|
|
id: '' |
|
|
|
}; |
|
|
|
}) |
|
|
|
); |
|
|
|
@ -673,7 +672,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { |
|
|
|
dataSource, |
|
|
|
name, |
|
|
|
symbol, |
|
|
|
assetSubClassString: translate(assetSubClass), |
|
|
|
assetSubClassString: translate(assetSubClass ?? ''), |
|
|
|
mode: SearchMode.ASSET_PROFILE as const |
|
|
|
}; |
|
|
|
} |
|
|
|
@ -705,7 +704,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { |
|
|
|
dataSource, |
|
|
|
name, |
|
|
|
symbol, |
|
|
|
assetSubClassString: translate(assetSubClass), |
|
|
|
assetSubClassString: translate(assetSubClass ?? ''), |
|
|
|
mode: SearchMode.HOLDING as const |
|
|
|
}; |
|
|
|
} |
|
|
|
@ -755,6 +754,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { |
|
|
|
const symbol = this.user?.settings?.['filters.symbol']; |
|
|
|
const selectedHolding = this.holdings.find((holding) => { |
|
|
|
return ( |
|
|
|
!!(dataSource && symbol) && |
|
|
|
getAssetProfileIdentifier({ |
|
|
|
dataSource: holding.dataSource, |
|
|
|
symbol: holding.symbol |
|
|
|
|