Browse Source

Refactoring

pull/7146/head
Thomas Kaul 3 weeks ago
parent
commit
2b0a92c159
  1. 113
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts
  2. 116
      libs/ui/src/lib/assistant/assistant.component.ts
  3. 1
      libs/ui/src/lib/portfolio-filter-form/index.ts
  4. 124
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.util.ts

113
apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts

@ -1,14 +1,16 @@
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { CreateAccessDto, UpdateAccessDto } from '@ghostfolio/common/dtos';
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import { Filter, PortfolioPosition } from '@ghostfolio/common/interfaces';
import { AccountWithPlatform } from '@ghostfolio/common/types';
import { validateObjectForForm } from '@ghostfolio/common/utils';
import { translate } from '@ghostfolio/ui/i18n';
import { NotificationService } from '@ghostfolio/ui/notifications';
import {
GfPortfolioFilterFormComponent,
PortfolioFilterFormValue
getAssetClassFilters,
getFiltersFromPortfolioFilterFormValue,
getHoldingsForFilter,
getPortfolioFilterFormValue,
getTagFilters
} from '@ghostfolio/ui/portfolio-filter-form';
import { DataService } from '@ghostfolio/ui/services';
@ -39,7 +41,7 @@ import {
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { AccessPermission, AssetClass, DataSource } from '@prisma/client';
import { AccessPermission } from '@prisma/client';
import { StatusCodes } from 'http-status-codes';
import { EMPTY, catchError } from 'rxjs';
@ -112,17 +114,7 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
]
});
this.assetClasses = Object.keys(AssetClass)
.map((assetClass) => {
return {
id: assetClass,
label: translate(assetClass),
type: 'ASSET_CLASS' as const
};
})
.sort((a, b) => {
return a.label.localeCompare(b.label);
});
this.assetClasses = getAssetClassFilters();
this.userService
.get()
@ -130,22 +122,7 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
.subscribe(({ accounts, settings, tags }) => {
this.accounts = accounts;
this.hasExperimentalFeatures = settings.isExperimentalFeatures ?? false;
this.tags =
tags
?.filter(({ isUsed }) => {
return isUsed;
})
?.map(({ id, name }) => {
return {
id,
label: translate(name),
type: 'TAG' as const
};
})
?.sort((a, b) => {
return a.label.localeCompare(b.label);
}) ?? [];
this.tags = getTagFilters(tags);
this.changeDetectorRef.markForCheck();
});
@ -197,41 +174,9 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
}
private buildFilters(): Filter[] {
const filterValue = this.accessForm.get('filters')
?.value as PortfolioFilterFormValue | null;
if (!filterValue) {
return [];
}
const filters: Filter[] = [];
if (filterValue.account) {
filters.push({ id: filterValue.account, type: 'ACCOUNT' });
}
if (filterValue.assetClass) {
filters.push({ id: filterValue.assetClass, type: 'ASSET_CLASS' });
}
if (filterValue.holding) {
filters.push(
{
id: filterValue.holding.assetProfile.dataSource,
type: 'DATA_SOURCE'
},
{
id: filterValue.holding.assetProfile.symbol,
type: 'SYMBOL'
}
);
}
if (filterValue.tag) {
filters.push({ id: filterValue.tag, type: 'TAG' });
}
return filters;
return getFiltersFromPortfolioFilterFormValue(
this.accessForm.get('filters')?.value
);
}
private loadHoldings() {
@ -239,18 +184,7 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
.fetchPortfolioHoldings()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ holdings }) => {
this.holdings = holdings
.filter(({ assetProfile }) => {
return (
assetProfile.assetSubClass &&
!['CASH'].includes(assetProfile.assetSubClass)
);
})
.sort((a, b) => {
return (a.assetProfile.name ?? '').localeCompare(
b.assetProfile.name ?? ''
);
});
this.holdings = getHoldingsForFilter(holdings);
this.updateFiltersFormControl(this.data.access?.settings?.filters);
@ -348,25 +282,8 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
return;
}
const getFilterId = (type: Filter['type']) => {
return filters.find((filter) => filter.type === type)?.id ?? null;
};
const dataSource = getFilterId('DATA_SOURCE') as DataSource;
const symbol = getFilterId('SYMBOL');
const holding = this.holdings.find(({ assetProfile }) => {
return (
!!(dataSource && symbol) &&
getAssetProfileIdentifier(assetProfile) ===
getAssetProfileIdentifier({ dataSource, symbol })
);
});
this.accessForm.get('filters')?.setValue({
account: getFilterId('ACCOUNT'),
assetClass: getFilterId('ASSET_CLASS'),
holding: holding ?? null,
tag: getFilterId('TAG')
} satisfies PortfolioFilterFormValue);
this.accessForm
.get('filters')
?.setValue(getPortfolioFilterFormValue(filters, this.holdings));
}
}

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

@ -1,4 +1,3 @@
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';
@ -31,7 +30,6 @@ import { MatMenuTrigger } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
import { AssetClass, DataSource } from '@prisma/client';
import { differenceInYears, eachYearOfInterval, format } from 'date-fns';
import Fuse from 'fuse.js';
import { addIcons } from 'ionicons';
@ -56,7 +54,12 @@ import {
import { translate } from '../i18n';
import {
GfPortfolioFilterFormComponent,
PortfolioFilterFormValue
PortfolioFilterFormValue,
getAssetClassFilters,
getFiltersFromPortfolioFilterFormValue,
getHoldingsForFilter,
getPortfolioFilterFormValue,
getTagFilters
} from '../portfolio-filter-form';
import { GfAssistantListItemComponent } from './assistant-list-item/assistant-list-item.component';
import { SearchMode } from './enums/search-mode';
@ -194,17 +197,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
}
public ngOnInit() {
this.assetClasses = Object.keys(AssetClass)
.map((assetClass) => {
return {
id: assetClass,
label: translate(assetClass),
type: 'ASSET_CLASS'
} satisfies Filter;
})
.sort((a, b) => {
return a.label.localeCompare(b.label);
});
this.assetClasses = getAssetClassFilters();
this.searchFormControl.valueChanges
.pipe(
@ -437,21 +430,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
this.portfolioFilterFormControl.disable({ emitEvent: false });
}
this.tags =
this.user?.tags
?.filter(({ isUsed }) => {
return isUsed;
})
?.map(({ id, name }) => {
return {
id,
label: translate(name),
type: 'TAG'
} satisfies Filter;
})
?.sort((a, b) => {
return a.label.localeCompare(b.label);
}) ?? [];
this.tags = getTagFilters(this.user?.tags);
}
public initialize() {
@ -500,18 +479,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
.fetchPortfolioHoldings()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ holdings }) => {
this.holdings = holdings
.filter(({ assetProfile }) => {
return (
assetProfile.assetSubClass &&
!['CASH'].includes(assetProfile.assetSubClass)
);
})
.sort((a, b) => {
return (a.assetProfile.name ?? '').localeCompare(
b.assetProfile.name ?? ''
);
});
this.holdings = getHoldingsForFilter(holdings);
this.setPortfolioFilterFormValues();
@ -520,30 +488,12 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
}
public onApplyFilters() {
const filterValue = this.portfolioFilterFormControl.value;
this.filtersChanged.emit([
{
id: filterValue?.account ?? '',
type: 'ACCOUNT'
},
{
id: filterValue?.assetClass ?? '',
type: 'ASSET_CLASS'
},
{
id: filterValue?.holding?.assetProfile?.dataSource ?? '',
type: 'DATA_SOURCE'
},
{
id: filterValue?.holding?.assetProfile?.symbol ?? '',
type: 'SYMBOL'
},
{
id: filterValue?.tag ?? '',
type: 'TAG'
}
]);
this.filtersChanged.emit(
getFiltersFromPortfolioFilterFormValue(
this.portfolioFilterFormControl.value,
{ includeEmpty: true }
)
);
this.onCloseAssistant();
}
@ -769,25 +719,23 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
}
private setPortfolioFilterFormValues() {
const dataSource = this.user?.settings?.[
'filters.dataSource'
] as DataSource;
const symbol = this.user?.settings?.['filters.symbol'];
const selectedHolding = this.holdings.find((holding) => {
return (
!!(dataSource && symbol) &&
getAssetProfileIdentifier({
dataSource: holding.assetProfile.dataSource,
symbol: holding.assetProfile.symbol
}) === getAssetProfileIdentifier({ dataSource, symbol })
);
});
const settings = this.user?.settings;
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
});
const filters = [
{ id: settings?.['filters.accounts']?.[0] ?? '', type: 'ACCOUNT' },
{
id: settings?.['filters.assetClasses']?.[0] ?? '',
type: 'ASSET_CLASS'
},
{ id: settings?.['filters.dataSource'] ?? '', type: 'DATA_SOURCE' },
{ id: settings?.['filters.symbol'] ?? '', type: 'SYMBOL' },
{ id: settings?.['filters.tags']?.[0] ?? '', type: 'TAG' }
].filter(({ id }) => {
return !!id;
}) as Filter[];
this.portfolioFilterFormControl.setValue(
getPortfolioFilterFormValue(filters, this.holdings)
);
}
}

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

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

124
libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.util.ts

@ -0,0 +1,124 @@
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import { Filter, PortfolioPosition } from '@ghostfolio/common/interfaces';
import { AssetClass, DataSource, Tag } from '@prisma/client';
import { translate } from '../i18n';
import { PortfolioFilterFormValue } from './interfaces';
export function getAssetClassFilters(): Filter[] {
return Object.keys(AssetClass)
.map((assetClass) => {
return {
id: assetClass,
label: translate(assetClass),
type: 'ASSET_CLASS'
} satisfies Filter;
})
.sort((a, b) => {
return a.label.localeCompare(b.label);
});
}
export function getFiltersFromPortfolioFilterFormValue(
value: PortfolioFilterFormValue | null,
{ includeEmpty = false }: { includeEmpty?: boolean } = {}
): Filter[] {
const filters: Filter[] = [
{
id: value?.account ?? '',
type: 'ACCOUNT'
},
{
id: value?.assetClass ?? '',
type: 'ASSET_CLASS'
},
{
id: value?.holding?.assetProfile?.dataSource ?? '',
type: 'DATA_SOURCE'
},
{
id: value?.holding?.assetProfile?.symbol ?? '',
type: 'SYMBOL'
},
{
id: value?.tag ?? '',
type: 'TAG'
}
];
return includeEmpty
? filters
: filters.filter(({ id }) => {
return !!id;
});
}
export function getHoldingsForFilter(
holdings: PortfolioPosition[] = []
): PortfolioPosition[] {
return holdings
.filter(({ assetProfile }) => {
return (
assetProfile.assetSubClass &&
!['CASH'].includes(assetProfile.assetSubClass)
);
})
.sort((a, b) => {
return (a.assetProfile.name ?? '').localeCompare(
b.assetProfile.name ?? ''
);
});
}
export function getPortfolioFilterFormValue(
filters: Filter[],
holdings: PortfolioPosition[]
): PortfolioFilterFormValue {
const getFilterId = (type: Filter['type']) => {
return (
filters?.find((filter) => {
return filter.type === type;
})?.id ?? null
);
};
const dataSource = getFilterId('DATA_SOURCE') as DataSource;
const symbol = getFilterId('SYMBOL');
const holding = holdings.find(({ assetProfile }) => {
return (
!!(dataSource && symbol) &&
getAssetProfileIdentifier(assetProfile) ===
getAssetProfileIdentifier({ dataSource, symbol })
);
});
return {
account: getFilterId('ACCOUNT'),
assetClass: getFilterId('ASSET_CLASS'),
holding: holding ?? null,
tag: getFilterId('TAG')
};
}
export function getTagFilters(
tags: (Tag & { isUsed: boolean })[] = []
): Filter[] {
return (
tags
?.filter(({ isUsed }) => {
return isUsed;
})
?.map(({ id, name }) => {
return {
id,
label: translate(name),
type: 'TAG'
} satisfies Filter;
})
?.sort((a, b) => {
return a.label.localeCompare(b.label);
}) ?? []
);
}
Loading…
Cancel
Save