Browse Source

Merge branch 'main' into bugfix/update-account-balance-of-activity-without-account

pull/7402/head
Thomas Kaul 1 month ago
committed by GitHub
parent
commit
4bb9f9165f
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 16
      CHANGELOG.md
  2. 15
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  3. 8
      apps/api/src/events/events.module.ts
  4. 53
      apps/api/src/events/portfolio-changed.listener.ts
  5. 16
      apps/api/src/services/api/api.service.ts
  6. 7
      apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
  7. 9
      apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts
  8. 9
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  9. 15
      apps/client/src/app/pages/portfolio/fire/fire-page.component.ts
  10. 56
      apps/client/src/app/pages/portfolio/fire/fire-page.html
  11. 444
      apps/client/src/locales/messages.ca.xlf
  12. 444
      apps/client/src/locales/messages.de.xlf
  13. 444
      apps/client/src/locales/messages.es.xlf
  14. 444
      apps/client/src/locales/messages.fr.xlf
  15. 444
      apps/client/src/locales/messages.it.xlf
  16. 444
      apps/client/src/locales/messages.ja.xlf
  17. 444
      apps/client/src/locales/messages.ko.xlf
  18. 444
      apps/client/src/locales/messages.nl.xlf
  19. 444
      apps/client/src/locales/messages.pl.xlf
  20. 444
      apps/client/src/locales/messages.pt.xlf
  21. 444
      apps/client/src/locales/messages.tr.xlf
  22. 444
      apps/client/src/locales/messages.uk.xlf
  23. 426
      apps/client/src/locales/messages.xlf
  24. 444
      apps/client/src/locales/messages.zh.xlf
  25. 13
      libs/common/src/lib/helper.ts
  26. 6
      libs/ui/src/lib/fire-calculator/fire-calculator.component.html
  27. 15
      libs/ui/src/lib/fire-calculator/fire-calculator.component.ts
  28. 12
      package-lock.json
  29. 4
      package.json

16
CHANGELOG.md

@ -7,18 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Changed
- Upgraded `fuse.js` from version `7.3.0` to `7.5.0`
### Fixed
- Resolved an exception in the `POST api/v1/order` endpoint when creating an activity with the update account balance option but without an account
## 3.33.0 - 2026-07-25
### Added ### Added
- Added the stack trace logging for `MaxListenersExceededWarning` occurrences - Added the stack trace logging for `MaxListenersExceededWarning` occurrences
### Changed ### Changed
- Moved the support to create custom tags from experimental to general availability
- Recomputed the portfolio snapshot calculation in the background on a portfolio change
- Improved the deduplication of the portfolio snapshot calculation jobs by considering the filters
- Refactored the deprecated animation providers (`provideAnimations()` and `provideNoopAnimations()`) - Refactored the deprecated animation providers (`provideAnimations()` and `provideNoopAnimations()`)
- Improved the language localization for German (`de`)
- Improved the language localization for Polish (`pl`) - Improved the language localization for Polish (`pl`)
### Fixed ### Fixed
- Resolved an exception in the `POST api/v1/order` endpoint when creating an activity with the update account balance option but without an account - Fixed an issue with the localization in the _FIRE_ page
- Improved the spacing in the testimonial section on the landing page - Improved the spacing in the testimonial section on the landing page
## 3.32.0 - 2026-07-22 ## 3.32.0 - 2026-07-22

15
apps/api/src/app/portfolio/calculator/portfolio-calculator.ts

@ -1097,16 +1097,19 @@ export abstract class PortfolioCalculator {
let cachedPortfolioSnapshot: PortfolioSnapshot; let cachedPortfolioSnapshot: PortfolioSnapshot;
let isCachedPortfolioSnapshotExpired = false; let isCachedPortfolioSnapshotExpired = false;
const jobId = this.userId; const portfolioSnapshotKey = this.redisCacheService.getPortfolioSnapshotKey(
{
try {
const cachedPortfolioSnapshotValue = await this.redisCacheService.get(
this.redisCacheService.getPortfolioSnapshotKey({
filters: this.filters, filters: this.filters,
userId: this.userId userId: this.userId
}) }
); );
const jobId = portfolioSnapshotKey;
try {
const cachedPortfolioSnapshotValue =
await this.redisCacheService.get(portfolioSnapshotKey);
const { expiration, portfolioSnapshot }: PortfolioSnapshotValue = const { expiration, portfolioSnapshot }: PortfolioSnapshotValue =
JSON.parse(cachedPortfolioSnapshotValue); JSON.parse(cachedPortfolioSnapshotValue);

8
apps/api/src/events/events.module.ts

@ -1,9 +1,12 @@
import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { DataGatheringQueueModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; import { DataGatheringQueueModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
@ -13,11 +16,14 @@ import { PortfolioChangedListener } from './portfolio-changed.listener';
@Module({ @Module({
imports: [ imports: [
ActivitiesModule, ActivitiesModule,
ApiModule,
ConfigurationModule, ConfigurationModule,
DataGatheringQueueModule, DataGatheringQueueModule,
DataProviderModule, DataProviderModule,
ExchangeRateDataModule, ExchangeRateDataModule,
RedisCacheModule PortfolioSnapshotQueueModule,
RedisCacheModule,
UserModule
], ],
providers: [AssetProfileChangedListener, PortfolioChangedListener] providers: [AssetProfileChangedListener, PortfolioChangedListener]
}) })

53
apps/api/src/events/portfolio-changed.listener.ts

@ -1,4 +1,12 @@
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import {
PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW,
PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME,
PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS
} from '@ghostfolio/common/config';
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter'; import { OnEvent } from '@nestjs/event-emitter';
@ -14,7 +22,12 @@ export class PortfolioChangedListener {
private debounceTimers = new Map<string, NodeJS.Timeout>(); private debounceTimers = new Map<string, NodeJS.Timeout>();
public constructor(private readonly redisCacheService: RedisCacheService) {} public constructor(
private readonly apiService: ApiService,
private readonly portfolioSnapshotService: PortfolioSnapshotService,
private readonly redisCacheService: RedisCacheService,
private readonly userService: UserService
) {}
@OnEvent(PortfolioChangedEvent.getName()) @OnEvent(PortfolioChangedEvent.getName())
handlePortfolioChangedEvent(event: PortfolioChangedEvent) { handlePortfolioChangedEvent(event: PortfolioChangedEvent) {
@ -39,6 +52,44 @@ export class PortfolioChangedListener {
private async processPortfolioChanged({ userId }: { userId: string }) { private async processPortfolioChanged({ userId }: { userId: string }) {
this.logger.log(`Portfolio of user '${userId}' has changed`); this.logger.log(`Portfolio of user '${userId}' has changed`);
try {
await this.redisCacheService.removePortfolioSnapshotsByUserId({ userId }); await this.redisCacheService.removePortfolioSnapshotsByUserId({ userId });
const user = await this.userService.user({ id: userId });
if (!user) {
return;
}
const userSettings = user.settings.settings;
const filters = this.apiService.buildFiltersFromUserSettings({
userSettings
});
// Recompute in the background to avoid a cold start on the next request
await this.portfolioSnapshotService.addJobToQueue({
data: {
filters,
userId,
calculationType: userSettings.performanceCalculationType,
userCurrency: userSettings.baseCurrency
},
name: PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME,
opts: {
...PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS,
jobId: this.redisCacheService.getPortfolioSnapshotKey({
filters,
userId
}),
priority: PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW
}
});
} catch (error) {
this.logger.error(
`Portfolio snapshot of user '${userId}' could not be recomputed`,
error
);
}
} }
} }

16
apps/api/src/services/api/api.service.ts

@ -1,4 +1,4 @@
import { Filter } from '@ghostfolio/common/interfaces'; import { Filter, UserSettings } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
@ -89,4 +89,18 @@ export class ApiService {
return filters; return filters;
} }
public buildFiltersFromUserSettings({
userSettings
}: {
userSettings: UserSettings;
}): Filter[] {
return this.buildFiltersFromQueryParams({
filterByAccounts: userSettings?.['filters.accounts']?.[0],
filterByAssetClasses: userSettings?.['filters.assetClasses']?.[0],
filterByDataSource: userSettings?.['filters.dataSource'],
filterBySymbol: userSettings?.['filters.symbol'],
filterByTags: userSettings?.['filters.tags']?.[0]
});
}
} }

7
apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts

@ -581,9 +581,10 @@ export class GfHoldingDetailDialogComponent implements OnInit {
if (state?.user) { if (state?.user) {
this.user = state.user; this.user = state.user;
this.hasPermissionToCreateOwnTag = this.hasPermissionToCreateOwnTag = hasPermission(
hasPermission(this.user.permissions, permissions.createOwnTag) && this.user?.permissions,
(this.user?.settings?.isExperimentalFeatures ?? false); permissions.createOwnTag
);
this.tagsAvailable = this.tagsAvailable =
this.user?.tags?.map((tag) => { this.user?.tags?.map((tag) => {

9
apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts

@ -65,7 +65,7 @@ export class GfCreateOrUpdateAccountDialogComponent {
protected accountForm: FormGroup; protected accountForm: FormGroup;
protected currencies: string[] = []; protected currencies: string[] = [];
protected filteredPlatforms: Observable<Platform[]> | undefined; protected filteredPlatforms: Observable<Platform[]> | undefined;
protected hasPermissionToCreateOwnTag: boolean | undefined; protected hasPermissionToCreateOwnTag: boolean;
protected platforms: Platform[] = []; protected platforms: Platform[] = [];
protected tagsAvailable: Tag[] = []; protected tagsAvailable: Tag[] = [];
@ -82,9 +82,10 @@ export class GfCreateOrUpdateAccountDialogComponent {
const { currencies } = this.dataService.fetchInfo(); const { currencies } = this.dataService.fetchInfo();
this.currencies = currencies; this.currencies = currencies;
this.hasPermissionToCreateOwnTag = this.hasPermissionToCreateOwnTag = hasPermission(
this.data.user?.settings?.isExperimentalFeatures && this.data.user?.permissions,
hasPermission(this.data.user?.permissions, permissions.createOwnTag); permissions.createOwnTag
);
this.tagsAvailable = [ this.tagsAvailable = [
...(this.data.user?.tags ?? []), ...(this.data.user?.tags ?? []),

9
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -92,7 +92,7 @@ export class GfCreateOrUpdateActivityDialogComponent {
protected currentMarketPrice: number | null = null; protected currentMarketPrice: number | null = null;
protected defaultDateFormat: string; protected defaultDateFormat: string;
protected defaultLookupItems: LookupItem[] = []; protected defaultLookupItems: LookupItem[] = [];
protected hasPermissionToCreateOwnTag: boolean | undefined; protected hasPermissionToCreateOwnTag: boolean;
protected isLoading = false; protected isLoading = false;
protected readonly isToday = isToday; protected readonly isToday = isToday;
protected mode: 'create' | 'update'; protected mode: 'create' | 'update';
@ -120,9 +120,10 @@ export class GfCreateOrUpdateActivityDialogComponent {
public ngOnInit() { public ngOnInit() {
this.currencyOfAssetProfile = this.data.activity?.assetProfile?.currency; this.currencyOfAssetProfile = this.data.activity?.assetProfile?.currency;
this.hasPermissionToCreateOwnTag = this.hasPermissionToCreateOwnTag = hasPermission(
this.data.user?.settings?.isExperimentalFeatures && this.data.user?.permissions,
hasPermission(this.data.user?.permissions, permissions.createOwnTag); permissions.createOwnTag
);
this.locale = this.data.user.settings.locale ?? DEFAULT_LOCALE; this.locale = this.data.user.settings.locale ?? DEFAULT_LOCALE;
this.mode = this.data.activity?.id ? 'update' : 'create'; this.mode = this.data.activity?.id ? 'update' : 'create';

15
apps/client/src/app/pages/portfolio/fire/fire-page.component.ts

@ -1,6 +1,7 @@
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { SubscriptionType } from '@ghostfolio/common/enums'; import { SubscriptionType } from '@ghostfolio/common/enums';
import { formatMonthAndYear } from '@ghostfolio/common/helper';
import { import {
FireCalculationCompleteEvent, FireCalculationCompleteEvent,
FireWealth, FireWealth,
@ -77,6 +78,20 @@ export class GfFirePageComponent implements OnInit {
); );
private readonly userService = inject(UserService); private readonly userService = inject(UserService);
protected get retirementDateLabel(): string {
const retirementDate =
this.user?.settings?.retirementDate ?? this.retirementDate;
if (!retirementDate) {
return '';
}
return formatMonthAndYear({
date: new Date(retirementDate),
locale: this.user?.settings?.locale
});
}
public ngOnInit() { public ngOnInit() {
this.isLoading = true; this.isLoading = true;

56
apps/client/src/app/pages/portfolio/fire/fire-page.html

@ -66,9 +66,7 @@
<div [class.text-muted]="user?.subscription?.type === 'Basic'"> <div [class.text-muted]="user?.subscription?.type === 'Basic'">
<div class="mb-2"> <div class="mb-2">
<ng-container i18n <ng-container i18n
>If you retire today, you would be able to withdraw</ng-container >If you retire today, you would be able to withdraw
>
<ng-container>&nbsp;</ng-container>
<span class="font-weight-bold" <span class="font-weight-bold"
><gf-value ><gf-value
class="d-inline-block" class="d-inline-block"
@ -77,12 +75,9 @@
[unit]="user?.settings?.baseCurrency" [unit]="user?.settings?.baseCurrency"
[value]="withdrawalRatePerYear?.toNumber()" [value]="withdrawalRatePerYear?.toNumber()"
/> />
<ng-container>&nbsp;</ng-container> per year</span
<ng-container i18n>per year</ng-container></span
> >
<ng-container>&nbsp;</ng-container> or
<ng-container i18n>or</ng-container>
<ng-container>&nbsp;</ng-container>
<span class="font-weight-bold" <span class="font-weight-bold"
><gf-value ><gf-value
class="d-inline-block" class="d-inline-block"
@ -91,11 +86,8 @@
[unit]="user?.settings?.baseCurrency" [unit]="user?.settings?.baseCurrency"
[value]="withdrawalRatePerMonth?.toNumber()" [value]="withdrawalRatePerMonth?.toNumber()"
/> />
<ng-container>&nbsp;</ng-container> per month</span
<ng-container i18n>per month</ng-container></span >, based on your total assets of
>
<ng-container i18n>, based on your total assets of</ng-container>
<ng-container>&nbsp;</ng-container>
<span class="font-weight-bold" <span class="font-weight-bold"
><gf-value ><gf-value
class="d-inline-block" class="d-inline-block"
@ -103,10 +95,9 @@
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[unit]="user?.settings?.baseCurrency" [unit]="user?.settings?.baseCurrency"
[value]="fireWealth?.today.valueInBaseCurrency" [value]="fireWealth?.today.valueInBaseCurrency"
/> /></span>
</span> and a safe withdrawal rate (SWR) of</ng-container
<ng-container>&nbsp;</ng-container> >
<ng-container i18n>and a safe withdrawal rate (SWR) of</ng-container>
@if ( @if (
!hasImpersonationId && !hasImpersonationId &&
hasPermissionToUpdateUserSettings && hasPermissionToUpdateUserSettings &&
@ -137,16 +128,9 @@
@if (user?.settings?.isExperimentalFeatures) { @if (user?.settings?.isExperimentalFeatures) {
<div> <div>
<ng-container i18n>By</ng-container> <ng-container i18n
<ng-container>&nbsp;</ng-container> >By <span class="font-weight-bold">{{ retirementDateLabel }}</span
<span class="font-weight-bold">{{ >, this is projected to increase to
user?.settings?.retirementDate ?? retirementDate
| date: 'MMMM yyyy'
}}</span>
<ng-container i18n="@@page.fire.projected.1">,</ng-container>
<ng-container>&nbsp;</ng-container>
<ng-container i18n>this is projected to increase to</ng-container>
<ng-container>&nbsp;</ng-container>
<span class="font-weight-bold" <span class="font-weight-bold"
><gf-value ><gf-value
class="d-inline-block" class="d-inline-block"
@ -155,12 +139,9 @@
[unit]="user?.settings?.baseCurrency" [unit]="user?.settings?.baseCurrency"
[value]="withdrawalRatePerYearProjected?.toNumber()" [value]="withdrawalRatePerYearProjected?.toNumber()"
/> />
<ng-container>&nbsp;</ng-container> per year</span
<ng-container i18n>per year</ng-container></span
> >
<ng-container>&nbsp;</ng-container> or
<ng-container i18n>or</ng-container>
<ng-container>&nbsp;</ng-container>
<span class="font-weight-bold" <span class="font-weight-bold"
><gf-value ><gf-value
class="d-inline-block" class="d-inline-block"
@ -169,11 +150,8 @@
[unit]="user?.settings?.baseCurrency" [unit]="user?.settings?.baseCurrency"
[value]="withdrawalRatePerMonthProjected?.toNumber()" [value]="withdrawalRatePerMonthProjected?.toNumber()"
/> />
<ng-container>&nbsp;</ng-container> per month</span
<ng-container i18n>per month</ng-container></span >, assuming a
>
<ng-container i18n>, assuming a</ng-container>
<ng-container>&nbsp;</ng-container>
<span class="font-weight-bold" <span class="font-weight-bold"
><gf-value ><gf-value
class="d-inline-block" class="d-inline-block"
@ -182,8 +160,8 @@
[precision]="2" [precision]="2"
[value]="user?.settings?.annualInterestRate / 100" [value]="user?.settings?.annualInterestRate / 100"
/></span> /></span>
<ng-container>&nbsp;</ng-container> annual interest rate.</ng-container
<ng-container i18n>annual interest rate</ng-container>. >
</div> </div>
} }
</div> </div>

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

File diff suppressed because it is too large

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

File diff suppressed because it is too large

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

File diff suppressed because it is too large

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

File diff suppressed because it is too large

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

File diff suppressed because it is too large

444
apps/client/src/locales/messages.ja.xlf

File diff suppressed because it is too large

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

File diff suppressed because it is too large

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

File diff suppressed because it is too large

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

File diff suppressed because it is too large

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

File diff suppressed because it is too large

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

File diff suppressed because it is too large

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

File diff suppressed because it is too large

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

File diff suppressed because it is too large

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

File diff suppressed because it is too large

13
libs/common/src/lib/helper.ts

@ -233,6 +233,19 @@ export function extractNumberFromString({
} }
} }
export function formatMonthAndYear({
date,
locale
}: {
date: Date;
locale?: string;
}) {
return new Intl.DateTimeFormat(locale, {
month: 'long',
year: 'numeric'
}).format(date);
}
export function getAllActivityTypes(): ActivityType[] { export function getAllActivityTypes(): ActivityType[] {
return Object.values(ActivityType); return Object.values(ActivityType);
} }

6
libs/ui/src/lib/fire-calculator/fire-calculator.component.html

@ -30,11 +30,7 @@
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Retirement Date</mat-label> <mat-label i18n>Retirement Date</mat-label>
<div> <div>{{ retirementDateLabel }}</div>
{{
calculatorForm.get('retirementDate')?.value | date: 'MMMM yyyy'
}}
</div>
<input <input
class="d-none" class="d-none"
formControlName="retirementDate" formControlName="retirementDate"

15
libs/ui/src/lib/fire-calculator/fire-calculator.component.ts

@ -3,7 +3,7 @@ import {
transformTickToAbbreviation transformTickToAbbreviation
} from '@ghostfolio/common/chart-helper'; } from '@ghostfolio/common/chart-helper';
import { primaryColorRgb } from '@ghostfolio/common/config'; import { primaryColorRgb } from '@ghostfolio/common/config';
import { getLocale } from '@ghostfolio/common/helper'; import { formatMonthAndYear, getLocale } from '@ghostfolio/common/helper';
import { FireCalculationCompleteEvent } from '@ghostfolio/common/interfaces'; import { FireCalculationCompleteEvent } from '@ghostfolio/common/interfaces';
import { ColorScheme } from '@ghostfolio/common/types'; import { ColorScheme } from '@ghostfolio/common/types';
@ -208,6 +208,19 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy {
}); });
} }
protected get retirementDateLabel(): string {
const retirementDate = this.calculatorForm.get('retirementDate')?.value;
if (!retirementDate) {
return '';
}
return formatMonthAndYear({
date: retirementDate,
locale: this.locale
});
}
public ngOnChanges() { public ngOnChanges() {
if (isNumber(this.fireWealth) && this.fireWealth >= 0) { if (isNumber(this.fireWealth) && this.fireWealth >= 0) {
this.calculatorForm.setValue( this.calculatorForm.setValue(

12
package-lock.json

@ -1,12 +1,12 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "3.32.0", "version": "3.33.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ghostfolio", "name": "ghostfolio",
"version": "3.32.0", "version": "3.33.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
@ -70,7 +70,7 @@
"dotenv-expand": "12.0.3", "dotenv-expand": "12.0.3",
"envalid": "8.2.0", "envalid": "8.2.0",
"fast-redact": "3.5.0", "fast-redact": "3.5.0",
"fuse.js": "7.3.0", "fuse.js": "7.5.0",
"google-spreadsheet": "3.2.0", "google-spreadsheet": "3.2.0",
"helmet": "8.2.0", "helmet": "8.2.0",
"http-status-codes": "2.3.0", "http-status-codes": "2.3.0",
@ -21313,9 +21313,9 @@
} }
}, },
"node_modules/fuse.js": { "node_modules/fuse.js": {
"version": "7.3.0", "version": "7.5.0",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.3.0.tgz", "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.5.0.tgz",
"integrity": "sha512-plz8RVjfcDedTGfVngWH1jmJvBvAwi1v2jecfDerbEnMcmOYUEEwKFTHbNoCiYyzaK2Ws8lABkTCcRSqCY1q4w==", "integrity": "sha512-sQtrEfA+ez/3G0cCZecF70oqpCRttCexYUG4mUrtWL49ULUzUyxokt5kyqwtKzj1270RaKih+hcP3qLcumccow==",
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">=10" "node": ">=10"

4
package.json

@ -1,6 +1,6 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "3.32.0", "version": "3.33.0",
"homepage": "https://ghostfol.io", "homepage": "https://ghostfol.io",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio", "repository": "https://github.com/ghostfolio/ghostfolio",
@ -114,7 +114,7 @@
"dotenv-expand": "12.0.3", "dotenv-expand": "12.0.3",
"envalid": "8.2.0", "envalid": "8.2.0",
"fast-redact": "3.5.0", "fast-redact": "3.5.0",
"fuse.js": "7.3.0", "fuse.js": "7.5.0",
"google-spreadsheet": "3.2.0", "google-spreadsheet": "3.2.0",
"helmet": "8.2.0", "helmet": "8.2.0",
"http-status-codes": "2.3.0", "http-status-codes": "2.3.0",

Loading…
Cancel
Save