diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e257515e..019bf4b6a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,15 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
+### Added
+
+- Extended the export functionality by the tags
+- Extended the user endpoint `GET api/v1/user` by the activities count
+
+### Changed
+
+- Upgraded `prettier` from version `3.4.2` to `3.5.1`
+
### Fixed
- Improved the numeric comparison of strings in the value component
+
+## 2.140.0 - 2025-02-20
+
+### Changed
+
+- Reloaded the available tags after creating a custom tag in the holding detail dialog (experimental)
+- Improved the validation of the currency management in the admin control panel
+- Migrated the `@ghostfolio/client` components to control flow
+- Migrated the `@ghostfolio/ui` components to control flow
+- Improved the language localization for German (`de`)
+
+### Fixed
+
+- Improved the error handling in the `HttpResponseInterceptor`
+- Fixed an issue while using symbol profile overrides in the historical market data table of the admin control panel
- Added missing assets in _Storybook_ setup
## 2.139.1 - 2025-02-15
### Added
+- Extended the tooltip in the chart of the holdings tab on the home page by the allocation, change and performance
- Added a new static portfolio analysis rule: _Regional Market Cluster Risk_ (Asia-Pacific Markets)
- Added a new static portfolio analysis rule: _Regional Market Cluster Risk_ (Japan)
- Added support to create custom tags in the holding detail dialog (experimental)
diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts
index 142109725..ee79059f9 100644
--- a/apps/api/src/app/admin/admin.service.ts
+++ b/apps/api/src/app/admin/admin.service.ts
@@ -30,6 +30,7 @@ import {
EnhancedSymbolProfile,
Filter
} from '@ghostfolio/common/interfaces';
+import { Sector } from '@ghostfolio/common/interfaces/sector.interface';
import { MarketDataPreset } from '@ghostfolio/common/types';
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
@@ -259,7 +260,8 @@ export class AdminService {
},
scraperConfiguration: true,
sectors: true,
- symbol: true
+ symbol: true,
+ SymbolProfileOverrides: true
}
}),
this.prismaService.symbolProfile.count({ where })
@@ -313,11 +315,10 @@ export class AdminService {
name,
Order,
sectors,
- symbol
+ symbol,
+ SymbolProfileOverrides
}) => {
- const countriesCount = countries
- ? Object.keys(countries).length
- : 0;
+ let countriesCount = countries ? Object.keys(countries).length : 0;
const lastMarketPrice = lastMarketPriceMap.get(
getAssetProfileIdentifier({ dataSource, symbol })
@@ -331,7 +332,34 @@ export class AdminService {
);
})?._count ?? 0;
- const sectorsCount = sectors ? Object.keys(sectors).length : 0;
+ let sectorsCount = sectors ? Object.keys(sectors).length : 0;
+
+ if (SymbolProfileOverrides) {
+ assetClass = SymbolProfileOverrides.assetClass ?? assetClass;
+ assetSubClass =
+ SymbolProfileOverrides.assetSubClass ?? assetSubClass;
+
+ if (
+ (
+ SymbolProfileOverrides.countries as unknown as Prisma.JsonArray
+ )?.length > 0
+ ) {
+ countriesCount = (
+ SymbolProfileOverrides.countries as unknown as Prisma.JsonArray
+ ).length;
+ }
+
+ name = SymbolProfileOverrides.name ?? name;
+
+ if (
+ (SymbolProfileOverrides.sectors as unknown as Sector[])
+ ?.length > 0
+ ) {
+ sectorsCount = (
+ SymbolProfileOverrides.sectors as unknown as Prisma.JsonArray
+ ).length;
+ }
+ }
return {
assetClass,
diff --git a/apps/api/src/app/endpoints/market-data/market-data.controller.ts b/apps/api/src/app/endpoints/market-data/market-data.controller.ts
index b4aef807a..933e70e9d 100644
--- a/apps/api/src/app/endpoints/market-data/market-data.controller.ts
+++ b/apps/api/src/app/endpoints/market-data/market-data.controller.ts
@@ -1,6 +1,7 @@
import { AdminService } from '@ghostfolio/api/app/admin/admin.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
+import { getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper';
import { MarketDataDetailsResponse } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types';
@@ -42,7 +43,7 @@ export class MarketDataController {
{ dataSource, symbol }
]);
- if (!assetProfile) {
+ if (!assetProfile && !isCurrency(getCurrencyFromSymbol(symbol))) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
@@ -55,7 +56,7 @@ export class MarketDataController {
);
const canReadOwnAssetProfile =
- assetProfile.userId === this.request.user.id &&
+ assetProfile?.userId === this.request.user.id &&
hasPermission(
this.request.user.permissions,
permissions.readMarketDataOfOwnAssetProfile
diff --git a/apps/api/src/app/export/export.module.ts b/apps/api/src/app/export/export.module.ts
index 048c60359..892a761cc 100644
--- a/apps/api/src/app/export/export.module.ts
+++ b/apps/api/src/app/export/export.module.ts
@@ -1,6 +1,7 @@
import { AccountModule } from '@ghostfolio/api/app/account/account.module';
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
+import { TagModule } from '@ghostfolio/api/services/tag/tag.module';
import { Module } from '@nestjs/common';
@@ -8,7 +9,7 @@ import { ExportController } from './export.controller';
import { ExportService } from './export.service';
@Module({
- imports: [AccountModule, ApiModule, OrderModule],
+ imports: [AccountModule, ApiModule, OrderModule, TagModule],
controllers: [ExportController],
providers: [ExportService]
})
diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts
index 1ff18ce9c..219ffffda 100644
--- a/apps/api/src/app/export/export.service.ts
+++ b/apps/api/src/app/export/export.service.ts
@@ -1,6 +1,7 @@
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { environment } from '@ghostfolio/api/environments/environment';
+import { TagService } from '@ghostfolio/api/services/tag/tag.service';
import { Filter, Export } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common';
@@ -9,7 +10,8 @@ import { Injectable } from '@nestjs/common';
export class ExportService {
public constructor(
private readonly accountService: AccountService,
- private readonly orderService: OrderService
+ private readonly orderService: OrderService,
+ private readonly tagService: TagService
) {}
public async export({
@@ -60,9 +62,21 @@ export class ExportService {
});
}
+ const tags = (await this.tagService.getTagsForUser(userId))
+ .filter(({ isUsed }) => {
+ return isUsed;
+ })
+ .map(({ id, name }) => {
+ return {
+ id,
+ name
+ };
+ });
+
return {
meta: { date: new Date().toISOString(), version: environment.version },
accounts,
+ tags,
activities: activities.map(
({
accountId,
@@ -72,6 +86,7 @@ export class ExportService {
id,
quantity,
SymbolProfile,
+ tags: currentTags,
type,
unitPrice
}) => {
@@ -86,13 +101,12 @@ export class ExportService {
currency: SymbolProfile.currency,
dataSource: SymbolProfile.dataSource,
date: date.toISOString(),
- symbol:
- type === 'FEE' ||
- type === 'INTEREST' ||
- type === 'ITEM' ||
- type === 'LIABILITY'
- ? SymbolProfile.name
- : SymbolProfile.symbol
+ symbol: ['FEE', 'INTEREST', 'ITEM', 'LIABILITY'].includes(type)
+ ? SymbolProfile.name
+ : SymbolProfile.symbol,
+ tags: currentTags.map(({ id: tagId }) => {
+ return tagId;
+ })
};
}
),
diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts
index dcf9d9404..40bc1b2b5 100644
--- a/apps/api/src/app/user/user.service.ts
+++ b/apps/api/src/app/user/user.service.ts
@@ -86,6 +86,9 @@ export class UserService {
orderBy: { alias: 'asc' },
where: { GranteeUser: { id } }
}),
+ this.prismaService.order.count({
+ where: { userId: id }
+ }),
this.prismaService.order.findFirst({
orderBy: {
date: 'asc'
@@ -96,8 +99,9 @@ export class UserService {
]);
const access = userData[0];
- const firstActivity = userData[1];
- let tags = userData[2];
+ const activitiesCount = userData[1];
+ const firstActivity = userData[2];
+ let tags = userData[3];
let systemMessage: SystemMessage;
@@ -117,6 +121,7 @@ export class UserService {
}
return {
+ activitiesCount,
id,
permissions,
subscription,
diff --git a/apps/api/src/services/symbol-profile/symbol-profile.service.ts b/apps/api/src/services/symbol-profile/symbol-profile.service.ts
index df0526d9f..0dae63311 100644
--- a/apps/api/src/services/symbol-profile/symbol-profile.service.ts
+++ b/apps/api/src/services/symbol-profile/symbol-profile.service.ts
@@ -204,8 +204,7 @@ export class SymbolProfileService {
?.length > 0
) {
item.countries = this.getCountries(
- item.SymbolProfileOverrides
- ?.countries as unknown as Prisma.JsonArray
+ item.SymbolProfileOverrides.countries as unknown as Prisma.JsonArray
);
}
@@ -214,22 +213,22 @@ export class SymbolProfileService {
?.length > 0
) {
item.holdings = this.getHoldings(
- item.SymbolProfileOverrides?.holdings as unknown as Prisma.JsonArray
+ item.SymbolProfileOverrides.holdings as unknown as Prisma.JsonArray
);
}
- item.name = item.SymbolProfileOverrides?.name ?? item.name;
+ item.name = item.SymbolProfileOverrides.name ?? item.name;
if (
(item.SymbolProfileOverrides.sectors as unknown as Sector[])?.length >
0
) {
item.sectors = this.getSectors(
- item.SymbolProfileOverrides?.sectors as unknown as Prisma.JsonArray
+ item.SymbolProfileOverrides.sectors as unknown as Prisma.JsonArray
);
}
- item.url = item.SymbolProfileOverrides?.url ?? item.url;
+ item.url = item.SymbolProfileOverrides.url ?? item.url;
delete item.SymbolProfileOverrides;
}
diff --git a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts
index fa5e33f10..b0f69fa5c 100644
--- a/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts
+++ b/apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts
@@ -15,9 +15,11 @@ import {
FormControl,
FormGroup,
ValidationErrors,
+ ValidatorFn,
Validators
} from '@angular/forms';
import { MatDialogRef } from '@angular/material/dialog';
+import { isISO4217CurrencyCode } from 'class-validator';
import { uniq } from 'lodash';
import { Subject, takeUntil } from 'rxjs';
@@ -52,9 +54,7 @@ export class CreateAssetProfileDialog implements OnInit, OnDestroy {
this.createAssetProfileForm = this.formBuilder.group(
{
addCurrency: new FormControl(null, [
- Validators.maxLength(3),
- Validators.minLength(3),
- Validators.required
+ this.iso4217CurrencyCodeValidator()
]),
addSymbol: new FormControl(null, [Validators.required]),
searchSymbol: new FormControl(null, [Validators.required])
@@ -83,11 +83,11 @@ export class CreateAssetProfileDialog implements OnInit, OnDestroy {
symbol: this.createAssetProfileForm.get('searchSymbol').value.symbol
});
} else if (this.mode === 'currency') {
- const currency = this.createAssetProfileForm
- .get('addCurrency')
- .value.toUpperCase();
+ const currency = (
+ this.createAssetProfileForm.get('addCurrency').value as string
+ ).toUpperCase();
- const currencies = uniq([...this.customCurrencies, currency]);
+ const currencies = uniq([...this.customCurrencies, currency]).sort();
this.dataService
.putAdminSetting(PROPERTY_CURRENCIES, {
@@ -109,10 +109,7 @@ export class CreateAssetProfileDialog implements OnInit, OnDestroy {
const addCurrencyFormControl =
this.createAssetProfileForm.get('addCurrency');
- if (
- addCurrencyFormControl.hasError('maxlength') ||
- addCurrencyFormControl.hasError('minlength')
- ) {
+ if (addCurrencyFormControl.hasError('invalidCurrency')) {
return true;
}
@@ -161,4 +158,14 @@ export class CreateAssetProfileDialog implements OnInit, OnDestroy {
this.changeDetectorRef.markForCheck();
});
}
+
+ private iso4217CurrencyCodeValidator(): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ if (!isISO4217CurrencyCode(control.value?.toUpperCase())) {
+ return { invalidCurrency: true };
+ }
+
+ return null;
+ };
+ }
}
diff --git a/apps/client/src/app/components/admin-overview/admin-overview.component.ts b/apps/client/src/app/components/admin-overview/admin-overview.component.ts
index d217f871d..f54af4174 100644
--- a/apps/client/src/app/components/admin-overview/admin-overview.component.ts
+++ b/apps/client/src/app/components/admin-overview/admin-overview.component.ts
@@ -28,7 +28,6 @@ import {
formatDistanceToNowStrict,
parseISO
} from 'date-fns';
-import { uniq } from 'lodash';
import { StringValue } from 'ms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@@ -122,24 +121,6 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
this.putAdminSetting({ key: PROPERTY_COUPONS, value: coupons });
}
- public onAddCurrency() {
- const currency = prompt($localize`Please add a currency:`);
-
- if (currency) {
- if (currency.length === 3) {
- const currencies = uniq([
- ...this.customCurrencies,
- currency.toUpperCase()
- ]);
- this.putAdminSetting({ key: PROPERTY_CURRENCIES, value: currencies });
- } else {
- this.notificationService.alert({
- title: $localize`${currency} is an invalid currency!`
- });
- }
- }
- }
-
public onChangeCouponDuration(aCouponDuration: StringValue) {
this.couponDuration = aCouponDuration;
}
diff --git a/apps/client/src/app/components/admin-overview/admin-overview.html b/apps/client/src/app/components/admin-overview/admin-overview.html
index ba8545d16..a85c32d43 100644
--- a/apps/client/src/app/components/admin-overview/admin-overview.html
+++ b/apps/client/src/app/components/admin-overview/admin-overview.html
@@ -95,16 +95,6 @@
}
-
-
-
diff --git a/apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts b/apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts
index 8c2907064..e42e6259e 100644
--- a/apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts
+++ b/apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts
@@ -3,7 +3,6 @@ import { DataService } from '@ghostfolio/client/services/data.service';
import { PROPERTY_API_KEY_GHOSTFOLIO } from '@ghostfolio/common/config';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
-import { CommonModule } from '@angular/common';
import { Component, Inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import {
@@ -18,7 +17,6 @@ import { GhostfolioPremiumApiDialogParams } from './interfaces/interfaces';
@Component({
imports: [
- CommonModule,
GfDialogFooterModule,
GfDialogHeaderModule,
GfPremiumIndicatorComponent,
diff --git a/apps/client/src/app/components/asset-profile-icon/asset-profile-icon.component.ts b/apps/client/src/app/components/asset-profile-icon/asset-profile-icon.component.ts
index 5db862969..179193da0 100644
--- a/apps/client/src/app/components/asset-profile-icon/asset-profile-icon.component.ts
+++ b/apps/client/src/app/components/asset-profile-icon/asset-profile-icon.component.ts
@@ -1,4 +1,3 @@
-import { CommonModule } from '@angular/common';
import {
CUSTOM_ELEMENTS_SCHEMA,
ChangeDetectionStrategy,
@@ -10,7 +9,6 @@ import { DataSource } from '@prisma/client';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
- imports: [CommonModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-asset-profile-icon',
styleUrls: ['./asset-profile-icon.component.scss'],
diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
index 69322b216..25317e0c5 100644
--- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
+++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
@@ -175,6 +175,9 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
]
});
}),
+ switchMap(() => {
+ return this.userService.get(true);
+ }),
takeUntil(this.unsubscribeSubject)
)
.subscribe();
diff --git a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts
index 7ee9c66cf..b57bcb0fd 100644
--- a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts
+++ b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts
@@ -1,6 +1,5 @@
import { XRayRulesSettings } from '@ghostfolio/common/interfaces';
-import { CommonModule } from '@angular/common';
import { Component, Inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
@@ -14,13 +13,7 @@ import { MatSliderModule } from '@angular/material/slider';
import { IRuleSettingsDialogParams } from './interfaces/interfaces';
@Component({
- imports: [
- CommonModule,
- FormsModule,
- MatButtonModule,
- MatDialogModule,
- MatSliderModule
- ],
+ imports: [FormsModule, MatButtonModule, MatDialogModule, MatSliderModule],
selector: 'gf-rule-settings-dialog',
styleUrls: ['./rule-settings-dialog.scss'],
templateUrl: './rule-settings-dialog.html'
diff --git a/apps/client/src/app/core/http-response.interceptor.ts b/apps/client/src/app/core/http-response.interceptor.ts
index 018e441fc..62c3540f7 100644
--- a/apps/client/src/app/core/http-response.interceptor.ts
+++ b/apps/client/src/app/core/http-response.interceptor.ts
@@ -108,10 +108,12 @@ export class HttpResponseInterceptor implements HttpInterceptor {
});
}
} else if (error.status === StatusCodes.UNAUTHORIZED) {
- if (this.webAuthnService.isEnabled()) {
- this.router.navigate(['/webauthn']);
- } else if (!error.url.includes('/data-providers/ghostfolio/status')) {
- this.tokenStorageService.signOut();
+ if (!error.url.includes('/data-providers/ghostfolio/status')) {
+ if (this.webAuthnService.isEnabled()) {
+ this.router.navigate(['/webauthn']);
+ } else {
+ this.tokenStorageService.signOut();
+ }
}
}
diff --git a/apps/client/src/app/core/notification/alert-dialog/alert-dialog.component.ts b/apps/client/src/app/core/notification/alert-dialog/alert-dialog.component.ts
index 8aefe84cf..98b6043eb 100644
--- a/apps/client/src/app/core/notification/alert-dialog/alert-dialog.component.ts
+++ b/apps/client/src/app/core/notification/alert-dialog/alert-dialog.component.ts
@@ -1,4 +1,3 @@
-import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
@@ -6,7 +5,7 @@ import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { IAlertDialogParams } from './interfaces/interfaces';
@Component({
- imports: [CommonModule, MatButtonModule, MatDialogModule],
+ imports: [MatButtonModule, MatDialogModule],
selector: 'gf-alert-dialog',
styleUrls: ['./alert-dialog.scss'],
templateUrl: './alert-dialog.html'
diff --git a/apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.component.ts b/apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.component.ts
index 9aced99cc..88e5113d7 100644
--- a/apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.component.ts
+++ b/apps/client/src/app/core/notification/confirmation-dialog/confirmation-dialog.component.ts
@@ -1,4 +1,3 @@
-import { CommonModule } from '@angular/common';
import { Component, HostListener } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
@@ -7,7 +6,7 @@ import { ConfirmationDialogType } from './confirmation-dialog.type';
import { IConfirmDialogParams } from './interfaces/interfaces';
@Component({
- imports: [CommonModule, MatButtonModule, MatDialogModule],
+ imports: [MatButtonModule, MatDialogModule],
selector: 'gf-confirmation-dialog',
styleUrls: ['./confirmation-dialog.scss'],
templateUrl: './confirmation-dialog.html'
diff --git a/apps/client/src/app/core/notification/prompt-dialog/prompt-dialog.component.ts b/apps/client/src/app/core/notification/prompt-dialog/prompt-dialog.component.ts
index 4ec634da9..6c8af4197 100644
--- a/apps/client/src/app/core/notification/prompt-dialog/prompt-dialog.component.ts
+++ b/apps/client/src/app/core/notification/prompt-dialog/prompt-dialog.component.ts
@@ -1,4 +1,3 @@
-import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
@@ -8,7 +7,6 @@ import { MatInputModule } from '@angular/material/input';
@Component({
imports: [
- CommonModule,
FormsModule,
MatButtonModule,
MatDialogModule,
diff --git a/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html b/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html
index 7538678c3..3b2f6f605 100644
--- a/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html
+++ b/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.html
@@ -56,6 +56,11 @@
Click on the + button
Switch to Add Currency
Insert e.g. EUR
for Euro
+ Select Filter by Currencies
+ Find the entry USDEUR
+
+ Click the menu item Gather Historical Data in the dialog
+
diff --git a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
index 3a0ec4ffb..6a8543e71 100644
--- a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
+++ b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
@@ -3,14 +3,13 @@ import { Product } from '@ghostfolio/common/interfaces';
import { personalFinanceTools } from '@ghostfolio/common/personal-finance-tools';
import { translate } from '@ghostfolio/ui/i18n';
-import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { ActivatedRoute, RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
- imports: [CommonModule, MatButtonModule, RouterModule],
+ imports: [MatButtonModule, RouterModule],
selector: 'gf-product-page',
styleUrls: ['./product-page.scss'],
templateUrl: './product-page.html'
diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf
index d70c773e6..a36fb152f 100644
--- a/apps/client/src/locales/messages.ca.xlf
+++ b/apps/client/src/locales/messages.ca.xlf
@@ -447,7 +447,7 @@
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 23
+ 22
@@ -583,7 +583,7 @@
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 24
+ 23
@@ -803,7 +803,7 @@
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 26
+ 25
@@ -1219,7 +1219,7 @@
apps/client/src/app/components/admin-overview/admin-overview.html
- 206
+ 196
apps/client/src/app/components/admin-platform/admin-platform.component.html
@@ -1958,28 +1958,12 @@
124
-
- Please add a currency:
- Si us plau, afegiu una divisa:
-
- apps/client/src/app/components/admin-overview/admin-overview.component.ts
- 126
-
-
-
- is an invalid currency!
- no és una divisa vàlida!
-
- apps/client/src/app/components/admin-overview/admin-overview.component.ts
- 137
-
-
Do you really want to delete this coupon?
Està segur qeu vol eliminar aquest cupó?
apps/client/src/app/components/admin-overview/admin-overview.component.ts
- 156
+ 137
@@ -1987,7 +1971,7 @@
Està segur que vol eliminar aquesta divisa?
apps/client/src/app/components/admin-overview/admin-overview.component.ts
- 169
+ 150
@@ -1995,7 +1979,7 @@
Està segur que vol eliminar aquest missatge del sistema?
apps/client/src/app/components/admin-overview/admin-overview.component.ts
- 182
+ 163
@@ -2003,7 +1987,7 @@
Està segur que vol depurar el cache?
apps/client/src/app/components/admin-overview/admin-overview.component.ts
- 206
+ 187
@@ -2011,7 +1995,7 @@
Si us plau, afegeixi el seu missatge del sistema:
apps/client/src/app/components/admin-overview/admin-overview.component.ts
- 226
+ 207
@@ -2061,17 +2045,13 @@
apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html
22
-
- apps/client/src/app/components/admin-overview/admin-overview.html
- 105
-
User Signup
Registrar Usuari
apps/client/src/app/components/admin-overview/admin-overview.html
- 111
+ 101
@@ -2079,7 +2059,7 @@
Mode Només Lecutra
apps/client/src/app/components/admin-overview/admin-overview.html
- 125
+ 115
@@ -2087,7 +2067,7 @@
Recollida de Dades
apps/client/src/app/components/admin-overview/admin-overview.html
- 137
+ 127
@@ -2095,7 +2075,7 @@
Missatge del Sistema
apps/client/src/app/components/admin-overview/admin-overview.html
- 149
+ 139
@@ -2103,7 +2083,7 @@
Estableix el Missatge
apps/client/src/app/components/admin-overview/admin-overview.html
- 171
+ 161
@@ -2111,7 +2091,7 @@
Coupons
apps/client/src/app/components/admin-overview/admin-overview.html
- 179
+ 169
@@ -2119,7 +2099,7 @@
Afegir
apps/client/src/app/components/admin-overview/admin-overview.html
- 239
+ 229
libs/ui/src/lib/account-balances/account-balances.component.html
@@ -2131,7 +2111,7 @@
Ordre
apps/client/src/app/components/admin-overview/admin-overview.html
- 247
+ 237
@@ -2139,7 +2119,7 @@
Depurar el Cache
apps/client/src/app/components/admin-overview/admin-overview.html
- 251
+ 241
@@ -2355,7 +2335,7 @@
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 94
+ 93
@@ -5095,7 +5075,7 @@
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 88
+ 87
@@ -5611,7 +5591,7 @@
Switzerland
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 59
+ 58
libs/ui/src/lib/i18n.ts
@@ -5623,7 +5603,7 @@
Global
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 60
+ 59
libs/ui/src/lib/i18n.ts
@@ -6775,7 +6755,7 @@
Alternative
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 82
+ 81
@@ -6783,7 +6763,7 @@
App
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 83
+ 82
@@ -6791,7 +6771,7 @@
Budgeting
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 84
+ 83
@@ -6799,7 +6779,7 @@
Community
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 85
+ 84
@@ -6807,7 +6787,7 @@
Family Office
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 86
+ 85
@@ -6815,7 +6795,7 @@
Investor
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 89
+ 88
@@ -6823,7 +6803,7 @@
Open Source
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 90
+ 89
@@ -6831,7 +6811,7 @@
Personal Finance
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 92
+ 91
@@ -6839,7 +6819,7 @@
Privacy
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 93
+ 92
@@ -6847,7 +6827,7 @@
Software
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 95
+ 94
@@ -6855,7 +6835,7 @@
Tool
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 96
+ 95
@@ -6863,7 +6843,7 @@
User Experience
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 97
+ 96
@@ -6871,7 +6851,7 @@
Wealth
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 98
+ 97
@@ -6879,7 +6859,7 @@
Wealth Management
apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts
- 99
+ 98
@@ -7633,7 +7613,7 @@
Please enter your Ghostfolio API key.
apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts
- 59
+ 57
@@ -7764,6 +7744,26 @@
374
+
+ Change
+ Change
+
+ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts
+ 365
+
+
+
+ Performance
+ Performance
+
+ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts
+ 365
+
+
+ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts
+ 378
+
+