Browse Source

Task/extract portfolio filter sub form of assistant to reusable component (#5618)

* Extract portfolio filter sub form of assistant to reusable component

* Update changelog
pull/5805/head^2
Germán Martín 5 days ago
committed by GitHub
parent
commit
3215280636
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 9
      libs/common/src/lib/interfaces/user.interface.ts
  3. 123
      libs/ui/src/lib/assistant/assistant.component.ts
  4. 170
      libs/ui/src/lib/assistant/assistant.html
  5. 2
      libs/ui/src/lib/portfolio-filter-form/index.ts
  6. 1
      libs/ui/src/lib/portfolio-filter-form/interfaces/index.ts
  7. 8
      libs/ui/src/lib/portfolio-filter-form/interfaces/portfolio-filter-form-value.interface.ts
  8. 75
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html
  9. 3
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.scss
  10. 79
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.stories.ts
  11. 177
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts

1
CHANGELOG.md

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Extracted the portfolio filter form of the assistant to a reusable component
- Formatted the holdings table in the _Copy AI prompt to clipboard for analysis_ action on the analysis page (experimental)
- Formatted the holdings table in the _Copy portfolio data to clipboard for AI prompt_ action on the analysis page (experimental)
- Improved the language localization for German (`de`)

9
libs/common/src/lib/interfaces/user.interface.ts

@ -1,6 +1,9 @@
import { SubscriptionType } from '@ghostfolio/common/types/subscription-type.type';
import {
AccountWithPlatform,
SubscriptionType
} from '@ghostfolio/common/types';
import { Access, Account, Tag } from '@prisma/client';
import { Access, Tag } from '@prisma/client';
import { SubscriptionOffer } from './subscription-offer.interface';
import { SystemMessage } from './system-message.interface';
@ -9,7 +12,7 @@ import { UserSettings } from './user-settings.interface';
// TODO: Compare with UserWithSettings
export interface User {
access: Pick<Access, 'alias' | 'id' | 'permissions'>[];
accounts: Account[];
accounts: AccountWithPlatform[];
activitiesCount: number;
dateOfFirstActivity: Date;
id: string;

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

@ -1,11 +1,10 @@
import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe';
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service';
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import { Filter, PortfolioPosition, User } from '@ghostfolio/common/interfaces';
import { InternalRoute } from '@ghostfolio/common/routes/interfaces/internal-route.interface';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { DateRange } from '@ghostfolio/common/types';
import { AccountWithPlatform, DateRange } from '@ghostfolio/common/types';
import { FocusKeyManager } from '@angular/cdk/a11y';
import {
@ -25,19 +24,14 @@ import {
ViewChild,
ViewChildren
} from '@angular/core';
import {
FormBuilder,
FormControl,
FormsModule,
ReactiveFormsModule
} from '@angular/forms';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatMenuTrigger } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
import { Account, AssetClass, DataSource } from '@prisma/client';
import { AssetClass, DataSource } from '@prisma/client';
import { differenceInYears } from 'date-fns';
import Fuse from 'fuse.js';
import { addIcons } from 'ionicons';
@ -60,8 +54,11 @@ import {
tap
} from 'rxjs/operators';
import { GfEntityLogoComponent } from '../entity-logo/entity-logo.component';
import { translate } from '../i18n';
import {
GfPortfolioFilterFormComponent,
PortfolioFilterFormValue
} from '../portfolio-filter-form';
import { GfAssistantListItemComponent } from './assistant-list-item/assistant-list-item.component';
import { SearchMode } from './enums/search-mode';
import {
@ -75,8 +72,7 @@ import {
imports: [
FormsModule,
GfAssistantListItemComponent,
GfEntityLogoComponent,
GfSymbolPipe,
GfPortfolioFilterFormComponent,
IonIcon,
MatButtonModule,
MatFormFieldModule,
@ -141,16 +137,10 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
public static readonly SEARCH_RESULTS_DEFAULT_LIMIT = 5;
public accounts: Account[] = [];
public accounts: AccountWithPlatform[] = [];
public assetClasses: Filter[] = [];
public dateRangeFormControl = new FormControl<string>(undefined);
public dateRangeOptions: DateRangeOption[] = [];
public filterForm = this.formBuilder.group({
account: new FormControl<string>(undefined),
assetClass: new FormControl<string>(undefined),
holding: new FormControl<PortfolioPosition>(undefined),
tag: new FormControl<string>(undefined)
});
public holdings: PortfolioPosition[] = [];
public isLoading = {
accounts: false,
@ -160,6 +150,14 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
};
public isOpen = false;
public placeholder = $localize`Find account, holding or page...`;
public portfolioFilterFormControl = new FormControl<PortfolioFilterFormValue>(
{
account: null,
assetClass: null,
holding: null,
tag: null
}
);
public searchFormControl = new FormControl('');
public searchResults: SearchResults = {
accounts: [],
@ -186,8 +184,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
public constructor(
private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private formBuilder: FormBuilder
private dataService: DataService
) {
addIcons({ closeCircleOutline, closeOutline, searchOutline });
}
@ -244,7 +241,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
);
}
// Accounts
const accounts$: Observable<Partial<SearchResults>> =
this.searchAccounts(searchTerm).pipe(
map((accounts) => ({
@ -263,7 +259,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
})
);
// Asset profiles
const assetProfiles$: Observable<Partial<SearchResults>> = this
.hasPermissionToAccessAdminControl
? this.searchAssetProfiles(searchTerm).pipe(
@ -292,7 +287,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
})
);
// Holdings
const holdings$: Observable<Partial<SearchResults>> =
this.searchHoldings(searchTerm).pipe(
map((holdings) => ({
@ -311,7 +305,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
})
);
// Quick links
const quickLinks$: Observable<Partial<SearchResults>> = of(
this.searchQuickLinks(searchTerm)
).pipe(
@ -327,7 +320,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
})
);
// Merge all results
return merge(accounts$, assetProfiles$, holdings$, quickLinks$).pipe(
scan(
(acc: SearchResults, curr: Partial<SearchResults>) => ({
@ -362,22 +354,11 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
quickLinks: []
};
this.changeDetectorRef.markForCheck();
},
complete: () => {
this.isLoading = {
accounts: false,
assetProfiles: false,
holdings: false,
quickLinks: false
};
this.changeDetectorRef.markForCheck();
}
});
}
public ngOnChanges() {
this.accounts = this.user?.accounts ?? [];
this.dateRangeOptions = [
{
label: $localize`Today`,
@ -445,7 +426,11 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
this.dateRangeFormControl.setValue(this.user?.settings?.dateRange ?? null);
this.filterForm.disable({ emitEvent: false });
if (this.hasPermissionToChangeFilters) {
this.portfolioFilterFormControl.enable({ emitEvent: false });
} else {
this.portfolioFilterFormControl.disable({ emitEvent: false });
}
this.tags =
this.user?.tags
@ -459,29 +444,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
type: 'TAG'
};
}) ?? [];
if (this.tags.length === 0) {
this.filterForm.get('tag').disable({ emitEvent: false });
}
}
public hasFilter(aFormValue: { [key: string]: string }) {
return Object.values(aFormValue).some((value) => {
return !!value;
});
}
public holdingComparisonFunction(
option: PortfolioPosition,
value: PortfolioPosition
): boolean {
if (value === null) {
return false;
}
return (
getAssetProfileIdentifier(option) === getAssetProfileIdentifier(value)
);
}
public initialize() {
@ -527,36 +489,35 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
.sort((a, b) => {
return a.name?.localeCompare(b.name);
});
this.setFilterFormValues();
if (this.hasPermissionToChangeFilters) {
this.filterForm.enable({ emitEvent: false });
}
this.setPortfolioFilterFormValues();
this.changeDetectorRef.markForCheck();
});
}
public onApplyFilters() {
const filterValue = this.portfolioFilterFormControl.value;
this.filtersChanged.emit([
{
id: this.filterForm.get('account').value,
id: filterValue?.account,
type: 'ACCOUNT'
},
{
id: this.filterForm.get('assetClass').value,
id: filterValue?.assetClass,
type: 'ASSET_CLASS'
},
{
id: this.filterForm.get('holding').value?.dataSource,
id: filterValue?.holding?.dataSource,
type: 'DATA_SOURCE'
},
{
id: this.filterForm.get('holding').value?.symbol,
id: filterValue?.holding?.symbol,
type: 'SYMBOL'
},
{
id: this.filterForm.get('tag').value,
id: filterValue?.tag,
type: 'TAG'
}
]);
@ -569,12 +530,15 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
}
public onCloseAssistant() {
this.portfolioFilterFormControl.reset();
this.setIsOpen(false);
this.closed.emit();
}
public onResetFilters() {
this.portfolioFilterFormControl.reset();
this.filtersChanged.emit(
this.filterTypes.map((type) => {
return {
@ -786,7 +750,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
});
}
private setFilterFormValues() {
private setPortfolioFilterFormValues() {
const dataSource = this.user?.settings?.[
'filters.dataSource'
] as DataSource;
@ -800,16 +764,11 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
);
});
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
}
);
this.portfolioFilterFormControl.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
});
}
}

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

@ -164,119 +164,61 @@
</div>
}
</div>
<form [formGroup]="filterForm">
@if (!searchFormControl.value) {
<div class="date-range-selector-container p-3">
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-label i18n>Date Range</mat-label>
<mat-select
[formControl]="dateRangeFormControl"
(selectionChange)="onChangeDateRange($event.value)"
>
@for (range of dateRangeOptions; track range) {
<mat-option [value]="range.value">{{ range.label }}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div class="p-3">
<div class="mb-3">
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-label i18n>Account</mat-label>
<mat-select formControlName="account">
<mat-option [value]="null" />
@for (account of accounts; track account.id) {
<mat-option [value]="account.id">
<div class="d-flex">
@if (account.platform?.url) {
<gf-entity-logo
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>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">
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-label i18n>Tag</mat-label>
<mat-select formControlName="tag">
<mat-option [value]="null" />
@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 Class</mat-label>
<mat-select formControlName="assetClass">
<mat-option [value]="null" />
@for (assetClass of assetClasses; track assetClass.id) {
<mat-option [value]="assetClass.id">{{
assetClass.label
}}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div class="d-flex w-100">
<button
i18n
mat-button
[disabled]="
!hasFilter(filterForm.value) || !hasPermissionToChangeFilters
"
(click)="onResetFilters()"
>
Reset Filters
</button>
<span class="gf-spacer"></span>
<button
color="primary"
i18n
mat-flat-button
[disabled]="!filterForm.dirty || !hasPermissionToChangeFilters"
(click)="onApplyFilters()"
>
Apply Filters
</button>
</div>
@if (!searchFormControl.value) {
<div class="date-range-selector-container p-3">
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-label i18n>Date Range</mat-label>
<mat-select
[formControl]="dateRangeFormControl"
(selectionChange)="onChangeDateRange($event.value)"
>
@for (
dateRangeOption of dateRangeOptions;
track dateRangeOption.value
) {
<mat-option [value]="dateRangeOption.value">{{
dateRangeOption.label
}}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
<div class="p-3">
<gf-portfolio-filter-form
#portfolioFilterForm
[accounts]="user?.accounts"
[assetClasses]="assetClasses"
[formControl]="portfolioFilterFormControl"
[holdings]="holdings"
[tags]="tags"
/>
<div class="d-flex w-100">
<button
i18n
mat-button
type="button"
[disabled]="
!portfolioFilterForm.hasFilters() || portfolioFilterForm.disabled
"
(click)="onResetFilters()"
>
Reset Filters
</button>
<span class="gf-spacer"></span>
<button
color="primary"
i18n
mat-flat-button
type="button"
[disabled]="
!portfolioFilterForm.filterForm.dirty ||
portfolioFilterForm.disabled
"
(click)="onApplyFilters()"
>
Apply Filters
</button>
</div>
}
</form>
</div>
}
</div>

2
libs/ui/src/lib/portfolio-filter-form/index.ts

@ -0,0 +1,2 @@
export * from './interfaces';
export * from './portfolio-filter-form.component';

1
libs/ui/src/lib/portfolio-filter-form/interfaces/index.ts

@ -0,0 +1 @@
export * from './portfolio-filter-form-value.interface';

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

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

75
libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html

@ -0,0 +1,75 @@
<form [formGroup]="filterForm">
<div class="mb-3">
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-label i18n>Account</mat-label>
<mat-select formControlName="account">
<mat-option [value]="null" />
@for (account of accounts; track account.id) {
<mat-option [value]="account.id">
<div class="d-flex">
@if (account.platform?.url) {
<gf-entity-logo
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>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">
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-label i18n>Tag</mat-label>
<mat-select formControlName="tag">
<mat-option [value]="null" />
@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 Class</mat-label>
<mat-select formControlName="assetClass">
<mat-option [value]="null" />
@for (assetClass of assetClasses; track assetClass.id) {
<mat-option [value]="assetClass.id">{{
assetClass.label
}}</mat-option>
}
</mat-select>
</mat-form-field>
</div>
</form>

3
libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.scss

@ -0,0 +1,3 @@
:host {
display: block;
}

79
libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.stories.ts

@ -0,0 +1,79 @@
import '@angular/localize/init';
import { Meta, moduleMetadata, StoryObj } from '@storybook/angular';
import { GfPortfolioFilterFormComponent } from './portfolio-filter-form.component';
const meta: Meta<GfPortfolioFilterFormComponent> = {
title: 'Portfolio Filter Form',
component: GfPortfolioFilterFormComponent,
decorators: [
moduleMetadata({
imports: [GfPortfolioFilterFormComponent]
})
]
};
export default meta;
type Story = StoryObj<GfPortfolioFilterFormComponent>;
export const Default: Story = {
args: {
accounts: [
{
id: '733110b6-7c55-44eb-8cc5-c4c3e9d48a79',
name: 'Trading Account',
platform: {
name: 'Interactive Brokers',
url: 'https://interactivebrokers.com'
}
},
{
id: '24ba27d6-e04b-4fb4-b856-b24c2ef0422a',
name: 'Investment Account',
platform: {
name: 'Fidelity',
url: 'https://fidelity.com'
}
}
] as any,
assetClasses: [
{ id: 'COMMODITY', label: 'Commodity', type: 'ASSET_CLASS' },
{ id: 'EQUITY', label: 'Equity', type: 'ASSET_CLASS' },
{ id: 'FIXED_INCOME', label: 'Fixed Income', type: 'ASSET_CLASS' }
] as any,
holdings: [
{
currency: 'USD',
dataSource: 'YAHOO',
name: 'Apple Inc.',
symbol: 'AAPL'
},
{
currency: 'USD',
dataSource: 'YAHOO',
name: 'Microsoft Corporation',
symbol: 'MSFT'
}
] as any,
tags: [
{
id: 'EMERGENCY_FUND',
label: 'Emergency Fund',
type: 'TAG'
},
{
id: 'RETIREMENT_FUND',
label: 'Retirement Fund',
type: 'TAG'
}
] as any,
disabled: false
}
};
export const Disabled: Story = {
args: {
...Default.args,
disabled: true
}
};

177
libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts

@ -0,0 +1,177 @@
import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe';
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import { Filter, PortfolioPosition } from '@ghostfolio/common/interfaces';
import { AccountWithPlatform } from '@ghostfolio/common/types';
import {
CUSTOM_ELEMENTS_SCHEMA,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
OnChanges,
OnDestroy,
OnInit,
forwardRef
} from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormControl,
FormGroup,
FormsModule,
NG_VALUE_ACCESSOR,
ReactiveFormsModule
} from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
import { Subject, takeUntil } from 'rxjs';
import { GfEntityLogoComponent } from '../entity-logo/entity-logo.component';
import { PortfolioFilterFormValue } from './interfaces';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
FormsModule,
GfEntityLogoComponent,
GfSymbolPipe,
MatFormFieldModule,
MatSelectModule,
ReactiveFormsModule
],
providers: [
{
multi: true,
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => GfPortfolioFilterFormComponent)
}
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-portfolio-filter-form',
styleUrls: ['./portfolio-filter-form.component.scss'],
templateUrl: './portfolio-filter-form.component.html'
})
export class GfPortfolioFilterFormComponent
implements ControlValueAccessor, OnInit, OnChanges, OnDestroy
{
@Input() accounts: AccountWithPlatform[] = [];
@Input() assetClasses: Filter[] = [];
@Input() holdings: PortfolioPosition[] = [];
@Input() tags: Filter[] = [];
@Input() disabled = false;
public filterForm: FormGroup;
private unsubscribeSubject = new Subject<void>();
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private formBuilder: FormBuilder
) {
this.filterForm = this.formBuilder.group({
account: new FormControl<string>(null),
assetClass: new FormControl<string>(null),
holding: new FormControl<PortfolioPosition>(null),
tag: new FormControl<string>(null)
});
}
public ngOnInit() {
this.filterForm.valueChanges
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((value) => {
this.onChange(value as PortfolioFilterFormValue);
this.onTouched();
});
}
public hasFilters() {
const formValue = this.filterForm.value;
return Object.values(formValue).some((value) => {
return !!value;
});
}
public holdingComparisonFunction(
option: PortfolioPosition,
value: PortfolioPosition
) {
if (value === null) {
return false;
}
return (
getAssetProfileIdentifier(option) === getAssetProfileIdentifier(value)
);
}
public ngOnChanges() {
if (this.disabled) {
this.filterForm.disable({ emitEvent: false });
} else {
this.filterForm.enable({ emitEvent: false });
}
const tagControl = this.filterForm.get('tag');
if (this.tags.length === 0) {
tagControl?.disable({ emitEvent: false });
} else if (!this.disabled) {
tagControl?.enable({ emitEvent: false });
}
this.changeDetectorRef.markForCheck();
}
public registerOnChange(fn: (value: PortfolioFilterFormValue) => void) {
this.onChange = fn;
}
public registerOnTouched(fn: () => void) {
this.onTouched = fn;
}
public setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
if (this.disabled) {
this.filterForm.disable({ emitEvent: false });
} else {
this.filterForm.enable({ emitEvent: false });
}
this.changeDetectorRef.markForCheck();
}
public writeValue(value: PortfolioFilterFormValue | null) {
if (value) {
this.filterForm.setValue(
{
account: value.account ?? null,
assetClass: value.assetClass ?? null,
holding: value.holding ?? null,
tag: value.tag ?? null
},
{ emitEvent: false }
);
} else {
this.filterForm.reset({}, { emitEvent: false });
}
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
private onChange = (_value: PortfolioFilterFormValue): void => {
// ControlValueAccessor onChange callback
};
private onTouched = (): void => {
// ControlValueAccessor onTouched callback
};
}
Loading…
Cancel
Save