Browse Source

Task/improve type safety in assistant components (#6396)

* feat(lib): resolve typescript errors in assistant

* feat(lib): resolve typescript errors in assistant list item

* feat(lib): implement output signals

* fix(lint): resolve warnings

* fix(lib): implement inject function

* fix(lib): handle errors in html files
pull/6403/head
Kenrick Tandrian 4 days ago
committed by GitHub
parent
commit
386a77c8f7
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 24
      libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts
  2. 2
      libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.html
  3. 102
      libs/ui/src/lib/assistant/assistant.component.ts
  4. 2
      libs/ui/src/lib/assistant/assistant.html
  5. 8
      libs/ui/src/lib/portfolio-filter-form/interfaces/portfolio-filter-form-value.interface.ts

24
libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts

@ -7,12 +7,12 @@ import {
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
HostBinding,
Input,
OnChanges,
Output,
ViewChild
ViewChild,
inject,
output
} from '@angular/core';
import { Params, RouterModule } from '@angular/router';
@ -33,21 +33,23 @@ export class GfAssistantListItemComponent
implements FocusableOption, OnChanges
{
@HostBinding('attr.tabindex') tabindex = -1;
@HostBinding('class.has-focus') get getHasFocus() {
return this.hasFocus;
}
@Input() item: SearchResultItem;
@Output() clicked = new EventEmitter<void>();
@ViewChild('link') public linkElement: ElementRef;
@ViewChild('link') public linkElement: ElementRef<HTMLAnchorElement>;
public hasFocus = false;
public queryParams: Params;
public routerLink: string[];
public constructor(private changeDetectorRef: ChangeDetectorRef) {}
protected readonly clicked = output<void>();
private readonly changeDetectorRef = inject(ChangeDetectorRef);
@HostBinding('class.has-focus')
public get getHasFocus() {
return this.hasFocus;
}
public ngOnChanges() {
if (this.item?.mode === SearchMode.ACCOUNT) {
@ -65,7 +67,7 @@ export class GfAssistantListItemComponent
};
this.routerLink =
internalRoutes.adminControl.subRoutes.marketData.routerLink;
internalRoutes.adminControl.subRoutes?.marketData.routerLink ?? [];
} else if (this.item?.mode === SearchMode.HOLDING) {
this.queryParams = {
dataSource: this.item.dataSource,

2
libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.html

@ -8,7 +8,7 @@
@if (item && isAsset(item)) {
<br />
<small class="text-muted"
>{{ item?.symbol | gfSymbol }} · {{ item?.currency }}
>{{ item?.symbol ?? '' | gfSymbol }} · {{ item?.currency }}
@if (item?.assetSubClassString) {
· {{ item.assetSubClassString }}
}

102
libs/ui/src/lib/assistant/assistant.component.ts

@ -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

2
libs/ui/src/lib/assistant/assistant.html

@ -186,7 +186,7 @@
<div class="p-3">
<gf-portfolio-filter-form
#portfolioFilterForm
[accounts]="user?.accounts"
[accounts]="user?.accounts ?? []"
[assetClasses]="assetClasses"
[formControl]="portfolioFilterFormControl"
[holdings]="holdings"

8
libs/ui/src/lib/portfolio-filter-form/interfaces/portfolio-filter-form-value.interface.ts

@ -1,8 +1,8 @@
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
export interface PortfolioFilterFormValue {
account: string;
assetClass: string;
holding: PortfolioPosition;
tag: string;
account: string | null;
assetClass: string | null;
holding: PortfolioPosition | null;
tag: string | null;
}

Loading…
Cancel
Save