Compare commits

...

7 Commits

Author SHA1 Message Date
github-actions[bot] 1cc4e50143 Update locales 2 days ago
Thomas Kaul 0fee18908f
Release 2.244.0 (#6409) 2 days ago
Thomas Kaul 98fb0b86af
Task/improve usability of asset profile details dialog for currencies (#6406) 2 days ago
Kenrick Tandrian 9493f79f8e
Task/improve type safety in portfolio filter form component (#6404) 2 days ago
Thomas Kaul f2c81ada90
Task/refactor $queryRawUnsafe() in data provider service (#6408) 2 days ago
github-actions[bot] 3f14e5ad3a
Task/update locales (#6356) 2 days ago
Thomas Kaul a7434c9ba7
Task/remove deprecated fee ratio X-ray rule (#6364) 2 days ago
  1. 8
      CHANGELOG.md
  2. 6
      apps/api/src/app/admin/admin.service.ts
  3. 8
      apps/api/src/app/portfolio/portfolio.service.ts
  4. 8
      apps/api/src/app/user/user.service.ts
  5. 104
      apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts
  6. 33
      apps/api/src/services/data-provider/data-provider.service.ts
  7. 24
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
  8. 11
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html
  9. 9
      apps/client/src/app/pages/i18n/i18n-page.html
  10. 282
      apps/client/src/locales/messages.ca.xlf
  11. 282
      apps/client/src/locales/messages.de.xlf
  12. 282
      apps/client/src/locales/messages.es.xlf
  13. 282
      apps/client/src/locales/messages.fr.xlf
  14. 282
      apps/client/src/locales/messages.it.xlf
  15. 282
      apps/client/src/locales/messages.ko.xlf
  16. 282
      apps/client/src/locales/messages.nl.xlf
  17. 282
      apps/client/src/locales/messages.pl.xlf
  18. 282
      apps/client/src/locales/messages.pt.xlf
  19. 282
      apps/client/src/locales/messages.tr.xlf
  20. 282
      apps/client/src/locales/messages.uk.xlf
  21. 279
      apps/client/src/locales/messages.xlf
  22. 282
      apps/client/src/locales/messages.zh.xlf
  23. 1
      libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts
  24. 12
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.html
  25. 4
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.stories.ts
  26. 69
      libs/ui/src/lib/portfolio-filter-form/portfolio-filter-form.component.ts
  27. 4
      package-lock.json
  28. 2
      package.json

8
CHANGELOG.md

@ -5,7 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## 2.244.0 - 2026-02-28
### Changed
- Improved the usability of the asset profile details dialog in the admin control panel for currencies
- Removed the deprecated static portfolio analysis rule: _Fees_ (Fee Ratio)
- Refactored queries in the data provider service to use Prisma’s safe query methods
### Fixed

6
apps/api/src/app/admin/admin.service.ts

@ -470,7 +470,9 @@ export class AdminService {
let currency: EnhancedSymbolProfile['currency'] = '-';
let dateOfFirstActivity: EnhancedSymbolProfile['dateOfFirstActivity'];
if (isCurrency(getCurrencyFromSymbol(symbol))) {
const isCurrencyAssetProfile = isCurrency(getCurrencyFromSymbol(symbol));
if (isCurrencyAssetProfile) {
currency = getCurrencyFromSymbol(symbol);
({ activitiesCount, dateOfFirstActivity } =
await this.orderService.getStatisticsByCurrency(currency));
@ -508,6 +510,8 @@ export class AdminService {
dataSource,
dateOfFirstActivity,
symbol,
assetClass: isCurrencyAssetProfile ? AssetClass.LIQUIDITY : undefined,
assetSubClass: isCurrencyAssetProfile ? AssetSubClass.CASH : undefined,
isActive: true
}
};

8
apps/api/src/app/portfolio/portfolio.service.ts

@ -13,7 +13,6 @@ import { CurrencyClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rul
import { EconomicMarketClusterRiskDevelopedMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/developed-markets';
import { EconomicMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/emerging-markets';
import { EmergencyFundSetup } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-setup';
import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment';
import { FeeRatioTotalInvestmentVolume } from '@ghostfolio/api/models/rules/fees/fee-ratio-total-investment-volume';
import { BuyingPower } from '@ghostfolio/api/models/rules/liquidity/buying-power';
import { RegionalMarketClusterRiskAsiaPacific } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/asia-pacific';
@ -1310,13 +1309,6 @@ export class PortfolioService {
}),
rules: await this.rulesService.evaluate(
[
new FeeRatioInitialInvestment(
this.exchangeRateDataService,
this.i18nService,
userSettings.language,
summary.committedFunds,
summary.fees
),
new FeeRatioTotalInvestmentVolume(
this.exchangeRateDataService,
this.i18nService,

8
apps/api/src/app/user/user.service.ts

@ -12,7 +12,6 @@ import { CurrencyClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rul
import { EconomicMarketClusterRiskDevelopedMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/developed-markets';
import { EconomicMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/emerging-markets';
import { EmergencyFundSetup } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-setup';
import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment';
import { FeeRatioTotalInvestmentVolume } from '@ghostfolio/api/models/rules/fees/fee-ratio-total-investment-volume';
import { BuyingPower } from '@ghostfolio/api/models/rules/liquidity/buying-power';
import { RegionalMarketClusterRiskAsiaPacific } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/asia-pacific';
@ -377,13 +376,6 @@ export class UserService {
undefined,
undefined
).getSettings(user.settings.settings),
FeeRatioInitialInvestment: new FeeRatioInitialInvestment(
undefined,
undefined,
undefined,
undefined,
undefined
).getSettings(user.settings.settings),
FeeRatioTotalInvestmentVolume: new FeeRatioTotalInvestmentVolume(
undefined,
undefined,

104
apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts

@ -1,104 +0,0 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces';
/**
* @deprecated This rule is deprecated in favor of FeeRatioTotalInvestmentVolume
*/
export class FeeRatioInitialInvestment extends Rule<Settings> {
private fees: number;
private totalInvestment: number;
public constructor(
protected exchangeRateDataService: ExchangeRateDataService,
private i18nService: I18nService,
languageCode: string,
totalInvestment: number,
fees: number
) {
super(exchangeRateDataService, {
languageCode,
key: FeeRatioInitialInvestment.name
});
this.fees = fees;
this.totalInvestment = totalInvestment;
}
public evaluate(ruleSettings: Settings) {
const feeRatio = this.totalInvestment
? this.fees / this.totalInvestment
: 0;
if (feeRatio > ruleSettings.thresholdMax) {
return {
evaluation: this.i18nService.getTranslation({
id: 'rule.feeRatioInitialInvestment.false',
languageCode: this.getLanguageCode(),
placeholders: {
feeRatio: (ruleSettings.thresholdMax * 100).toFixed(2),
thresholdMax: (feeRatio * 100).toPrecision(3)
}
}),
value: false
};
}
return {
evaluation: this.i18nService.getTranslation({
id: 'rule.feeRatioInitialInvestment.true',
languageCode: this.getLanguageCode(),
placeholders: {
feeRatio: (feeRatio * 100).toPrecision(3),
thresholdMax: (ruleSettings.thresholdMax * 100).toFixed(2)
}
}),
value: true
};
}
public getCategoryName() {
return this.i18nService.getTranslation({
id: 'rule.fees.category',
languageCode: this.getLanguageCode()
});
}
public getConfiguration() {
return {
threshold: {
max: 0.1,
min: 0,
step: 0.0025,
unit: '%'
},
thresholdMax: true
};
}
public getName() {
return this.i18nService.getTranslation({
id: 'rule.feeRatioInitialInvestment',
languageCode: this.getLanguageCode()
});
}
public getSettings({
baseCurrency,
locale,
xRayRules
}: UserSettings): Settings {
return {
baseCurrency,
locale,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.01
};
}
}
interface Settings extends RuleSettings {
baseCurrency: string;
thresholdMax: number;
}

33
apps/api/src/services/data-provider/data-provider.service.ts

@ -30,7 +30,7 @@ import {
import type { Granularity, UserWithSettings } from '@ghostfolio/common/types';
import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { DataSource, MarketData, SymbolProfile } from '@prisma/client';
import { DataSource, MarketData, Prisma, SymbolProfile } from '@prisma/client';
import { Big } from 'big.js';
import { eachDayOfInterval, format, isValid } from 'date-fns';
import { groupBy, isEmpty, isNumber, uniqWith } from 'lodash';
@ -347,36 +347,35 @@ export class DataProviderService implements OnModuleInit {
const granularityQuery =
aGranularity === 'month'
? `AND (date_part('day', date) = 1 OR date >= TIMESTAMP 'yesterday')`
: '';
? Prisma.sql`AND (date_part('day', date) = 1 OR date >= TIMESTAMP 'yesterday')`
: Prisma.empty;
const rangeQuery =
from && to
? `AND date >= '${format(from, DATE_FORMAT)}' AND date <= '${format(
? Prisma.sql`AND date >= ${format(from, DATE_FORMAT)}::timestamp AND date <= ${format(
to,
DATE_FORMAT
)}'`
: '';
)}::timestamp`
: Prisma.empty;
const dataSources = aItems.map(({ dataSource }) => {
return dataSource;
});
const symbols = aItems.map(({ symbol }) => {
return symbol;
});
try {
const queryRaw = `
SELECT *
FROM "MarketData"
WHERE "dataSource" IN ('${dataSources.join(`','`)}')
AND "symbol" IN ('${symbols.join(
`','`
)}') ${granularityQuery} ${rangeQuery}
ORDER BY date;`;
const marketDataByGranularity: MarketData[] =
await this.prismaService.$queryRawUnsafe(queryRaw);
const marketDataByGranularity: MarketData[] = await this.prismaService
.$queryRaw`
SELECT *
FROM "MarketData"
WHERE "dataSource"::text IN (${Prisma.join(dataSources)})
AND "symbol" IN (${Prisma.join(symbols)})
${granularityQuery}
${rangeQuery}
ORDER BY date;`;
response = marketDataByGranularity.reduce((r, marketData) => {
const { date, marketPrice, symbol } = marketData;

24
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts

@ -5,7 +5,11 @@ import {
PROPERTY_IS_DATA_GATHERING_ENABLED
} from '@ghostfolio/common/config';
import { UpdateAssetProfileDto } from '@ghostfolio/common/dtos';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import {
DATE_FORMAT,
getCurrencyFromSymbol,
isCurrency
} from '@ghostfolio/common/helper';
import {
AdminMarketDataDetails,
AssetClassSelectorOption,
@ -138,7 +142,6 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit {
});
public assetSubClassOptions: AssetClassSelectorOption[] = [];
public assetProfile: AdminMarketDataDetails['assetProfile'];
public assetProfileForm = this.formBuilder.group({
@ -180,12 +183,14 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit {
);
public benchmarks: Partial<SymbolProfile>[];
public canEditAssetProfile = true;
public countries: {
[code: string]: { name: string; value: number };
};
public currencies: string[] = [];
public dateRangeOptions = [
{
label: $localize`Current week` + ' (' + $localize`WTD` + ')',
@ -260,7 +265,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit {
}
public get canSaveAssetProfileIdentifier() {
return !this.assetProfileForm.dirty;
return !this.assetProfileForm.dirty && this.canEditAssetProfile;
}
public ngOnInit() {
@ -324,6 +329,11 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit {
this.assetClassLabel = translate(this.assetProfile?.assetClass);
this.assetSubClassLabel = translate(this.assetProfile?.assetSubClass);
this.canEditAssetProfile = !isCurrency(
getCurrencyFromSymbol(this.data.symbol)
);
this.countries = {};
this.isBenchmark = this.benchmarks.some(({ id }) => {
@ -390,6 +400,10 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit {
url: this.assetProfile?.url ?? ''
});
if (!this.canEditAssetProfile) {
this.assetProfileForm.disable();
}
this.assetProfileForm.markAsPristine();
this.changeDetectorRef.markForCheck();
@ -399,7 +413,9 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit {
public onCancelEditAssetProfileIdentifierMode() {
this.isEditAssetProfileIdentifierMode = false;
this.assetProfileForm.enable();
if (this.canEditAssetProfile) {
this.assetProfileForm.enable();
}
this.assetProfileIdentifierForm.reset();
}

11
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html

@ -300,6 +300,7 @@
</div>
<form
#assetProfileFormElement
[class.d-none]="!canEditAssetProfile"
[formGroup]="assetProfileForm"
(keyup.enter)="assetProfileForm.valid && onSubmitAssetProfileForm()"
(ngSubmit)="onSubmitAssetProfileForm()"
@ -358,7 +359,9 @@
<mat-checkbox
color="primary"
[checked]="isBenchmark"
[disabled]="isEditAssetProfileIdentifierMode"
[disabled]="
!canEditAssetProfile || isEditAssetProfileIdentifierMode
"
(change)="
isBenchmark
? onUnsetBenchmark({
@ -581,7 +584,11 @@
<mat-checkbox
color="primary"
[checked]="isDataGatheringEnabled && (assetProfile?.isActive ?? false)"
[disabled]="!isDataGatheringEnabled || isEditAssetProfileIdentifierMode"
[disabled]="
!canEditAssetProfile ||
!isDataGatheringEnabled ||
isEditAssetProfileIdentifierMode
"
(change)="onToggleIsActive($event)"
>
<ng-container i18n>Data Gathering</ng-container>

9
apps/client/src/app/pages/i18n/i18n-page.html

@ -149,15 +149,6 @@
<li i18n="@@rule.emergencyFundSetup.true">
An emergency fund has been set up
</li>
<li i18n="@@rule.feeRatioInitialInvestment">Fee Ratio (legacy)</li>
<li i18n="@@rule.feeRatioInitialInvestment.false">
The fees do exceed $&#123;thresholdMax&#125;% of your initial investment
($&#123;feeRatio&#125;%)
</li>
<li i18n="@@rule.feeRatioInitialInvestment.true">
The fees do not exceed $&#123;thresholdMax&#125;% of your initial
investment ($&#123;feeRatio&#125;%)
</li>
<li i18n="@@rule.feeRatioTotalInvestmentVolume">Fee Ratio</li>
<li i18n="@@rule.feeRatioTotalInvestmentVolume.false">
The fees do exceed $&#123;thresholdMax&#125;% of your total investment

282
apps/client/src/locales/messages.ca.xlf

File diff suppressed because it is too large

282
apps/client/src/locales/messages.de.xlf

File diff suppressed because it is too large

282
apps/client/src/locales/messages.es.xlf

File diff suppressed because it is too large

282
apps/client/src/locales/messages.fr.xlf

File diff suppressed because it is too large

282
apps/client/src/locales/messages.it.xlf

File diff suppressed because it is too large

282
apps/client/src/locales/messages.ko.xlf

File diff suppressed because it is too large

282
apps/client/src/locales/messages.nl.xlf

File diff suppressed because it is too large

282
apps/client/src/locales/messages.pl.xlf

File diff suppressed because it is too large

282
apps/client/src/locales/messages.pt.xlf

File diff suppressed because it is too large

282
apps/client/src/locales/messages.tr.xlf

File diff suppressed because it is too large

282
apps/client/src/locales/messages.uk.xlf

File diff suppressed because it is too large

279
apps/client/src/locales/messages.xlf

File diff suppressed because it is too large

282
apps/client/src/locales/messages.zh.xlf

File diff suppressed because it is too large

1
libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts

@ -9,7 +9,6 @@ export interface XRayRulesSettings {
EconomicMarketClusterRiskDevelopedMarkets?: RuleSettings;
EconomicMarketClusterRiskEmergingMarkets?: RuleSettings;
EmergencyFundSetup?: RuleSettings;
FeeRatioInitialInvestment?: RuleSettings;
FeeRatioTotalInvestmentVolume?: RuleSettings;
RegionalMarketClusterRiskAsiaPacific?: RuleSettings;
RegionalMarketClusterRiskEmergingMarkets?: RuleSettings;

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

@ -4,14 +4,14 @@
<mat-label i18n>Account</mat-label>
<mat-select formControlName="account">
<mat-option [value]="null" />
@for (account of accounts; track account.id) {
@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"
[tooltip]="account.platform?.name ?? ''"
[url]="account.platform?.url ?? ''"
/>
}
<span>{{ account.name }}</span>
@ -32,7 +32,7 @@
filterForm.get('holding')?.value?.name
}}</mat-select-trigger>
<mat-option [value]="null" />
@for (holding of holdings; track holding.name) {
@for (holding of holdings(); track holding.name) {
<mat-option [value]="holding">
<div class="line-height-1 text-truncate">
<span
@ -53,7 +53,7 @@
<mat-label i18n>Tag</mat-label>
<mat-select formControlName="tag">
<mat-option [value]="null" />
@for (tag of tags; track tag.id) {
@for (tag of tags(); track tag.id) {
<mat-option [value]="tag.id">{{ tag.label }}</mat-option>
}
</mat-select>
@ -64,7 +64,7 @@
<mat-label i18n>Asset Class</mat-label>
<mat-select formControlName="assetClass">
<mat-option [value]="null" />
@for (assetClass of assetClasses; track assetClass.id) {
@for (assetClass of assetClasses(); track assetClass.id) {
<mat-option [value]="assetClass.id">{{
assetClass.label
}}</mat-option>

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

@ -40,7 +40,7 @@ export const Default: Story = {
{ 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',
@ -66,7 +66,7 @@ export const Default: Story = {
label: 'Retirement Fund',
type: 'TAG'
}
] as any,
],
disabled: false
}
};

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

@ -8,12 +8,15 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Input,
DestroyRef,
OnChanges,
OnDestroy,
OnInit,
forwardRef
forwardRef,
inject,
input,
model
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
ControlValueAccessor,
FormBuilder,
@ -25,7 +28,6 @@ import {
} 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';
@ -53,33 +55,37 @@ import { PortfolioFilterFormValue } from './interfaces';
templateUrl: './portfolio-filter-form.component.html'
})
export class GfPortfolioFilterFormComponent
implements ControlValueAccessor, OnInit, OnChanges, OnDestroy
implements ControlValueAccessor, OnChanges, OnInit
{
@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
) {
public readonly accounts = input<AccountWithPlatform[]>([]);
public readonly assetClasses = input<Filter[]>([]);
public readonly disabled = model(false);
public readonly holdings = input<PortfolioPosition[]>([]);
public readonly tags = input<Filter[]>([]);
public filterForm: FormGroup<{
account: FormControl<string | null>;
assetClass: FormControl<string | null>;
holding: FormControl<PortfolioPosition | null>;
tag: FormControl<string | null>;
}>;
private readonly changeDetectorRef = inject(ChangeDetectorRef);
private readonly destroyRef = inject(DestroyRef);
private readonly formBuilder = inject(FormBuilder);
public constructor() {
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)
account: new FormControl<string | null>(null),
assetClass: new FormControl<string | null>(null),
holding: new FormControl<PortfolioPosition | null>(null),
tag: new FormControl<string | null>(null)
});
}
public ngOnInit() {
this.filterForm.valueChanges
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
this.onChange(value as PortfolioFilterFormValue);
this.onTouched();
@ -108,7 +114,7 @@ export class GfPortfolioFilterFormComponent
}
public ngOnChanges() {
if (this.disabled) {
if (this.disabled()) {
this.filterForm.disable({ emitEvent: false });
} else {
this.filterForm.enable({ emitEvent: false });
@ -116,9 +122,9 @@ export class GfPortfolioFilterFormComponent
const tagControl = this.filterForm.get('tag');
if (this.tags.length === 0) {
if (this.tags().length === 0) {
tagControl?.disable({ emitEvent: false });
} else if (!this.disabled) {
} else if (!this.disabled()) {
tagControl?.enable({ emitEvent: false });
}
@ -134,9 +140,9 @@ export class GfPortfolioFilterFormComponent
}
public setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
this.disabled.set(isDisabled);
if (this.disabled) {
if (this.disabled()) {
this.filterForm.disable({ emitEvent: false });
} else {
this.filterForm.enable({ emitEvent: false });
@ -161,11 +167,6 @@ export class GfPortfolioFilterFormComponent
}
}
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

4
package-lock.json

@ -1,12 +1,12 @@
{
"name": "ghostfolio",
"version": "2.243.0",
"version": "2.244.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ghostfolio",
"version": "2.243.0",
"version": "2.244.0",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {

2
package.json

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "2.243.0",
"version": "2.244.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio",

Loading…
Cancel
Save