diff --git a/.github/workflows/build-code.yml b/.github/workflows/build-code.yml index 7a8f72ac4..bd5d1efdc 100644 --- a/.github/workflows/build-code.yml +++ b/.github/workflows/build-code.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: node_version: - - 22 + - 22.22.1 steps: - name: Checkout code uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index edbfa5460..6624570cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,249 @@ 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.253.0 - 2026-03-06 + +### Added + +- Added support for filtering by activity type on the activities page (experimental) +- Extended the admin control panel by adding a copy-to-clipboard button for the application version + +### Changed + +- Extended the terms of service for the _Ghostfolio_ SaaS (cloud) to include _Paid Plans_ and _Refund Policy_ +- Upgraded `prisma` from version `6.19.0` to `6.19.3` + +### Fixed + +- Fixed the allocations by account chart on the allocations page in the _Presenter View_ +- Fixed the allocations by asset class chart on the allocations page in the _Presenter View_ +- Fixed the allocations by currency chart on the allocations page in the _Presenter View_ +- Fixed the allocations by ETF provider chart on the allocations page in the _Presenter View_ +- Fixed the allocations by platform chart on the allocations page in the _Presenter View_ + +## 2.252.0 - 2026-03-02 + +### Added + +- Added support for a copy-to-clipboard functionality in the value component +- Extended the holding detail dialog by adding a copy-to-clipboard button for the ISIN number (experimental) +- Extended the holding detail dialog by adding a copy-to-clipboard button for the symbol (experimental) +- Extended the user detail dialog of the admin control panel’s users section by adding a copy-to-clipboard button for the user id + +### Changed + +- Refreshed the cryptocurrencies list +- Improved the language localization for German (`de`) +- Improved the language localization for Spanish (`es`) +- Upgraded `countries-list` from version `3.2.2` to `3.3.0` +- Upgraded `ng-extract-i18n-merge` from `3.2.1` to `3.3.0` +- Upgraded `stripe` from version `20.3.0` to `20.4.1` + +## 2.251.0 - 2026-03-24 + +### Added + +- Added the quantity column to the holdings table of the portfolio holdings page + +### Changed + +- Hardened the endpoint `DELETE /api/v1/auth-device/:id` by improving the user validation +- Improved the allocations by ETF holding on the allocations page by refining the grouping of the same assets with diverging names (experimental) +- Improved the language localization for Polish (`pl`) +- Upgraded `@trivago/prettier-plugin-sort-imports` from version `5.2.2` to `6.0.2` + +### Fixed + +- Fixed an issue by adding a missing guard in the public access for portfolio sharing + +## 2.250.0 - 2026-03-17 + +### Added + +- Added support for specific calendar year date ranges (`2025`, `2024`, `2023`, etc.) on the portfolio activities page + +### Changed + +- Consolidated the sign-out logic within the user service to unify cookie, state and token clearance +- Improved the language localization for Polish (`pl`) +- Upgraded `@ionic/angular` from version `8.7.3` to `8.8.1` +- Upgraded `replace-in-file` from version `8.3.0` to `8.4.0` +- Upgraded `svgmap` from version `2.14.0` to `2.19.2` +- Pinned the _Node.js_ version in the _Build code_ _GitHub Action_ to ensure environment consistency for tests + +### Fixed + +- Fixed an issue with the detection of the thousand separator for the `de-CH` locale +- Fixed an issue in the _Storybook_ stories of the symbol autocomplete component caused by a circular dependency + +## 2.249.0 - 2026-03-10 + +### Added + +- Integrated _Bull Dashboard_ for a detailed jobs queue view in the admin control panel (experimental) +- Added a debounce to the `PortfolioChangedListener` and `AssetProfileChangedListener` to minimize redundant _Redis_ and database operations ### Changed +- Improved the _Storybook_ stories of the value component +- Improved the language localization for Dutch (`nl`) +- Improved the language localization for German (`de`) +- Upgraded `class-validator` from version `0.14.3` to `0.15.1` + +### Fixed + +- Fixed false _Redis_ health check failures by using unique keys and increasing the timeout to 5s + +## 2.248.0 - 2026-03-07 + +### Added + +- Added support for column sorting to the data providers management of the admin control panel + +### Changed + +- Included asset profile data in the endpoint `GET api/v1/portfolio/holdings` +- Included asset profile data in the holdings of the public page +- Reused the value component in the platform management of the admin control panel +- Reused the value component in the tag management of the admin control panel +- Deprecated the `api/v1/order` endpoints in favor of the `api/v1/activities` endpoints +- Upgraded `jsonpath` from version `1.1.1` to `1.2.1` + +### Fixed + +- Fixed an issue in the _FIRE_ calculator to correctly calculate the projected total amount + +## 2.247.0 - 2026-03-04 + +### Changed + +- Upgraded `yahoo-finance2` from version `3.13.0` to `3.13.2` + +## 2.246.0 - 2026-03-03 + +### Changed + +- Removed the deprecated `committedFunds` from the summary of the portfolio details endpoint +- Upgraded `Nx` from version `22.4.5` to `22.5.3` + +### Fixed + +- Fixed an issue where the apply and reset filter buttons remained disabled in the assistant + +## 2.245.0 - 2026-03-01 + +### Changed + +- Excluded the scraper configuration from the import and export functionality +- Excluded the symbol mapping from the import and export functionality +- Improved the language localization for Dutch (`nl`) +- Improved the language localization for Italian (`it`) +- Improved the language localization for Spanish (`es`) + +### Fixed + +- Resolved the data source transformation in the errors of the performance endpoint +- Resolved the data source transformation in the export functionality + +## 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 + +- Fixed an exception by adding a fallback for missing market price values on the _X-ray_ page + +## 2.243.0 - 2026-02-23 + +### Changed + +- Improved the language localization for Chinese (`zh`) +- Upgraded `nestjs` from version `11.1.8` to `11.1.14` + +### Fixed + +- Fixed an issue when creating activities of type `FEE`, `INTEREST` or `LIABILITY` + +## 2.242.0 - 2026-02-22 + +### Changed + +- Changed the account field to optional in the create or update activity dialog + +### Fixed + +- Fixed a validation issue for valuables used in the create and import activity logic +- Fixed the page size for presets in the historical market data table of the admin control panel + +## 2.241.0 - 2026-02-21 + +### Changed + +- Improved the usability of the portfolio summary tab on the home page in the _Presenter View_ +- Refreshed the cryptocurrencies list +- Improved the language localization for German (`de`) +- Improved the language localization for Spanish (`es`) + +### Fixed + +- Fixed an issue with `balanceInBaseCurrency` of the accounts in the value redaction interceptor for the impersonation mode +- Fixed an issue with `comment` of the accounts in the value redaction interceptor for the impersonation mode +- Fixed an issue with `dividendInBaseCurrency` of the accounts in the value redaction interceptor for the impersonation mode +- Fixed an issue with `interestInBaseCurrency` of the accounts in the value redaction interceptor for the impersonation mode +- Fixed an issue with `value` of the accounts in the value redaction interceptor for the impersonation mode + +## 2.240.0 - 2026-02-18 + +### Added + +- Added a _No Activities_ preset to the historical market data table of the admin control panel +- Added support for custom cryptocurrencies defined in the database +- Added support for the cryptocurrency _Sky_ + +### Changed + +- Harmonized the validation for the create activity endpoint with the existing import activity logic +- Upgraded `marked` from version `17.0.1` to `17.0.2` +- Upgraded `ngx-markdown` from version `21.0.1` to `21.1.0` + +## 2.239.0 - 2026-02-15 + +### Added + +- Added a new static portfolio analysis rule based on the total investment volume: _Fees_ (Fee Ratio) +- Extended the content of the _Self-Hosting_ section on the Frequently Asked Questions (FAQ) page with information on derived currencies + +### Changed + +- Deprecated the existing static portfolio analysis rule: _Fees_ (Fee Ratio) +- Ignored nested ETFs when fetching top holdings for ETF and mutual fund assets from _Yahoo Finance_ +- Improved the scraper configuration with more detailed error messages +- Improved the language localization for German (`de`) +- Upgraded `@simplewebauthn/browser` and `@simplewebauthn/server` from version `13.1.0` to `13.2.2` +- Upgraded `cheerio` from version `1.0.0` to `1.2.0` + +### Fixed + +- Fixed the investment value by including currency effects in the portfolio summary tab on the home page +- Added the missing `valueInBaseCurrency` to the response of the import activities endpoint + +## 2.238.0 - 2026-02-12 + +### Changed + +- Upgraded `ngx-skeleton-loader` from version `11.3.0` to `12.0.0` - Upgraded `twitter-api-v2` from version `1.27.0` to `1.29.0` +### Fixed + +- Fixed a performance calculation issue by resetting tracking variables when a holding is fully closed +- Fixed an issue in the annualized performance calculation +- Fixed an issue with the exchange rate calculation by expanding the date range to cover the full day (start to end of day) + ## 2.237.0 - 2026-02-08 ### Changed diff --git a/README.md b/README.md index 3be15e49f..44212b607 100644 --- a/README.md +++ b/README.md @@ -313,10 +313,12 @@ Ghostfolio is **100% free** and **open source**. We encourage and support an act Not sure what to work on? We have [some ideas](https://github.com/ghostfolio/ghostfolio/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22%20no%3Aassignee), even for [newcomers](https://github.com/ghostfolio/ghostfolio/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%20no%3Aassignee). Please join the Ghostfolio [Slack](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg) channel or post to [@ghostfolio\_](https://x.com/ghostfolio_) on _X_. We would love to hear from you. -If you like to support this project, become a [**Sponsor**](https://github.com/sponsors/ghostfolio), get [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) or [**Buy me a coffee**](https://www.buymeacoffee.com/ghostfolio). - ## Sponsors +If you like to support this project, get [**Ghostfolio Premium**](https://ghostfol.io/en/pricing), become a [**Sponsor**](https://github.com/sponsors/ghostfolio) or [**Buy me a coffee**](https://www.buymeacoffee.com/ghostfolio). + +
+
TestMu AI Logo diff --git a/apps/api/src/app/order/order.controller.ts b/apps/api/src/app/activities/activities.controller.ts similarity index 76% rename from apps/api/src/app/order/order.controller.ts rename to apps/api/src/app/activities/activities.controller.ts index 73c295f1b..6b0440dc4 100644 --- a/apps/api/src/app/order/order.controller.ts +++ b/apps/api/src/app/activities/activities.controller.ts @@ -4,6 +4,7 @@ import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; import { ApiService } from '@ghostfolio/api/services/api/api.service'; +import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service'; import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; @@ -36,27 +37,32 @@ import { } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; -import { Order as OrderModel, Prisma } from '@prisma/client'; +import { Order, Prisma, Type as ActivityType } from '@prisma/client'; import { parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; -import { OrderService } from './order.service'; +import { ActivitiesService } from './activities.service'; -@Controller('order') -export class OrderController { +@Controller([ + 'activities', + /** @deprecated */ + 'order' +]) +export class ActivitiesController { public constructor( + private readonly activitiesService: ActivitiesService, private readonly apiService: ApiService, + private readonly dataProviderService: DataProviderService, private readonly dataGatheringService: DataGatheringService, private readonly impersonationService: ImpersonationService, - private readonly orderService: OrderService, @Inject(REQUEST) private readonly request: RequestWithUser ) {} @Delete() - @HasPermission(permissions.deleteOrder) + @HasPermission(permissions.deleteActivity) @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(TransformDataSourceInRequestInterceptor) - public async deleteOrders( + public async deleteActivities( @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, @Query('dataSource') filterByDataSource?: string, @@ -71,29 +77,29 @@ export class OrderController { filterByTags }); - return this.orderService.deleteOrders({ + return this.activitiesService.deleteActivities({ filters, userId: this.request.user.id }); } @Delete(':id') - @HasPermission(permissions.deleteOrder) + @HasPermission(permissions.deleteActivity) @UseGuards(AuthGuard('jwt'), HasPermissionGuard) - public async deleteOrder(@Param('id') id: string): Promise { - const order = await this.orderService.order({ + public async deleteActivity(@Param('id') id: string): Promise { + const activity = await this.activitiesService.order({ id, userId: this.request.user.id }); - if (!order) { + if (!activity) { throw new HttpException( getReasonPhrase(StatusCodes.FORBIDDEN), StatusCodes.FORBIDDEN ); } - return this.orderService.deleteOrder({ + return this.activitiesService.deleteActivity({ id }); } @@ -103,9 +109,10 @@ export class OrderController { @UseInterceptors(RedactValuesInResponseInterceptor) @UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) - public async getAllOrders( + public async getAllActivities( @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Query('accounts') filterByAccounts?: string, + @Query('activityTypes') filterByTypes?: string, @Query('assetClasses') filterByAssetClasses?: string, @Query('dataSource') filterByDataSource?: string, @Query('range') dateRange?: DateRange, @@ -120,7 +127,7 @@ export class OrderController { let startDate: Date; if (dateRange) { - ({ endDate, startDate } = getIntervalFromDateRange(dateRange)); + ({ endDate, startDate } = getIntervalFromDateRange({ dateRange })); } const filters = this.apiService.buildFiltersFromQueryParams({ @@ -133,14 +140,18 @@ export class OrderController { const impersonationUserId = await this.impersonationService.validateImpersonationId(impersonationId); + + const types = (filterByTypes?.split(',') as ActivityType[]) ?? []; + const userCurrency = this.request.user.settings.settings.baseCurrency; - const { activities, count } = await this.orderService.getOrders({ + const { activities, count } = await this.activitiesService.getActivities({ endDate, filters, sortColumn, sortDirection, startDate, + types, userCurrency, includeDrafts: true, skip: isNaN(skip) ? undefined : skip, @@ -156,7 +167,7 @@ export class OrderController { @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(RedactValuesInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) - public async getOrderById( + public async getActivityById( @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Param('id') id: string ): Promise { @@ -164,7 +175,7 @@ export class OrderController { await this.impersonationService.validateImpersonationId(impersonationId); const userCurrency = this.request.user.settings.settings.baseCurrency; - const { activities } = await this.orderService.getOrders({ + const { activities } = await this.activitiesService.getActivities({ userCurrency, includeDrafts: true, userId: impersonationUserId || this.request.user.id, @@ -185,11 +196,34 @@ export class OrderController { return activity; } - @HasPermission(permissions.createOrder) + @HasPermission(permissions.createActivity) @Post() @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(TransformDataSourceInRequestInterceptor) - public async createOrder(@Body() data: CreateOrderDto): Promise { + public async createActivity(@Body() data: CreateOrderDto): Promise { + try { + await this.dataProviderService.validateActivities({ + activitiesDto: [ + { + currency: data.currency, + dataSource: data.dataSource, + symbol: data.symbol, + type: data.type + } + ], + maxActivitiesToImport: 1, + user: this.request.user + }); + } catch (error) { + throw new HttpException( + { + error: getReasonPhrase(StatusCodes.BAD_REQUEST), + message: [error.message] + }, + StatusCodes.BAD_REQUEST + ); + } + const currency = data.currency; const customCurrency = data.customCurrency; const dataSource = data.dataSource; @@ -202,7 +236,7 @@ export class OrderController { delete data.dataSource; - const order = await this.orderService.createOrder({ + const activity = await this.activitiesService.createActivity({ ...data, date: parseISO(data.date), SymbolProfile: { @@ -227,14 +261,14 @@ export class OrderController { userId: this.request.user.id }); - if (dataSource && !order.isDraft) { + if (dataSource && !activity.isDraft) { // Gather symbol data in the background, if data source is set // (not MANUAL) and not draft this.dataGatheringService.gatherSymbols({ dataGatheringItems: [ { dataSource, - date: order.date, + date: activity.date, symbol: data.symbol } ], @@ -242,19 +276,22 @@ export class OrderController { }); } - return order; + return activity; } - @HasPermission(permissions.updateOrder) + @HasPermission(permissions.updateActivity) @Put(':id') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(TransformDataSourceInRequestInterceptor) - public async update(@Param('id') id: string, @Body() data: UpdateOrderDto) { - const originalOrder = await this.orderService.order({ + public async updateActivity( + @Param('id') id: string, + @Body() data: UpdateOrderDto + ) { + const originalActivity = await this.activitiesService.order({ id }); - if (!originalOrder || originalOrder.userId !== this.request.user.id) { + if (!originalActivity || originalActivity.userId !== this.request.user.id) { throw new HttpException( getReasonPhrase(StatusCodes.FORBIDDEN), StatusCodes.FORBIDDEN @@ -277,7 +314,7 @@ export class OrderController { delete data.dataSource; - return this.orderService.updateOrder({ + return this.activitiesService.updateActivity({ data: { ...data, date, diff --git a/apps/api/src/app/order/order.module.ts b/apps/api/src/app/activities/activities.module.ts similarity index 86% rename from apps/api/src/app/order/order.module.ts rename to apps/api/src/app/activities/activities.module.ts index 9bc837aa6..7476ad66a 100644 --- a/apps/api/src/app/order/order.module.ts +++ b/apps/api/src/app/activities/activities.module.ts @@ -15,12 +15,12 @@ import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/sym import { Module } from '@nestjs/common'; -import { OrderController } from './order.controller'; -import { OrderService } from './order.service'; +import { ActivitiesController } from './activities.controller'; +import { ActivitiesService } from './activities.service'; @Module({ - controllers: [OrderController], - exports: [OrderService], + controllers: [ActivitiesController], + exports: [ActivitiesService], imports: [ ApiModule, CacheModule, @@ -35,6 +35,6 @@ import { OrderService } from './order.service'; TransformDataSourceInRequestModule, TransformDataSourceInResponseModule ], - providers: [AccountBalanceService, AccountService, OrderService] + providers: [AccountBalanceService, AccountService, ActivitiesService] }) -export class OrderModule {} +export class ActivitiesModule {} diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/activities/activities.service.ts similarity index 93% rename from apps/api/src/app/order/order.service.ts rename to apps/api/src/app/activities/activities.service.ts index 9a4f1e46b..58b9c11a4 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/activities/activities.service.ts @@ -44,7 +44,7 @@ import { groupBy, uniqBy } from 'lodash'; import { randomUUID } from 'node:crypto'; @Injectable() -export class OrderService { +export class ActivitiesService { public constructor( private readonly accountBalanceService: AccountBalanceService, private readonly accountService: AccountService, @@ -62,7 +62,7 @@ export class OrderService { tags, userId }: { tags: Tag[]; userId: string } & AssetProfileIdentifier) { - const orders = await this.prismaService.order.findMany({ + const activities = await this.prismaService.order.findMany({ where: { userId, SymbolProfile: { @@ -73,7 +73,7 @@ export class OrderService { }); await Promise.all( - orders.map(({ id }) => + activities.map(({ id }) => this.prismaService.order.update({ data: { tags: { @@ -96,7 +96,7 @@ export class OrderService { ); } - public async createOrder( + public async createActivity( data: Prisma.OrderCreateInput & { accountId?: string; assetClass?: AssetClass; @@ -201,7 +201,7 @@ export class OrderService { ? false : isAfter(data.date as Date, endOfToday()); - const order = await this.prismaService.order.create({ + const activity = await this.prismaService.order.create({ data: { ...orderData, account, @@ -235,56 +235,56 @@ export class OrderService { this.eventEmitter.emit( AssetProfileChangedEvent.getName(), new AssetProfileChangedEvent({ - currency: order.SymbolProfile.currency, - dataSource: order.SymbolProfile.dataSource, - symbol: order.SymbolProfile.symbol + currency: activity.SymbolProfile.currency, + dataSource: activity.SymbolProfile.dataSource, + symbol: activity.SymbolProfile.symbol }) ); this.eventEmitter.emit( PortfolioChangedEvent.getName(), new PortfolioChangedEvent({ - userId: order.userId + userId: activity.userId }) ); - return order; + return activity; } - public async deleteOrder( + public async deleteActivity( where: Prisma.OrderWhereUniqueInput ): Promise { - const order = await this.prismaService.order.delete({ + const activity = await this.prismaService.order.delete({ where }); const [symbolProfile] = await this.symbolProfileService.getSymbolProfilesByIds([ - order.symbolProfileId + activity.symbolProfileId ]); if (symbolProfile.activitiesCount === 0) { - await this.symbolProfileService.deleteById(order.symbolProfileId); + await this.symbolProfileService.deleteById(activity.symbolProfileId); } this.eventEmitter.emit( PortfolioChangedEvent.getName(), new PortfolioChangedEvent({ - userId: order.userId + userId: activity.userId }) ); - return order; + return activity; } - public async deleteOrders({ + public async deleteActivities({ filters, userId }: { filters?: Filter[]; userId: string; }): Promise { - const { activities } = await this.getOrders({ + const { activities } = await this.getActivities({ filters, userId, includeDrafts: true, @@ -324,7 +324,7 @@ export class OrderService { } /** - * Generates synthetic orders for cash holdings based on account balance history. + * Generates synthetic activities for cash holdings based on account balance history. * Treat currencies as assets with a fixed unit price of 1.0 (in their own currency) to allow * performance tracking based on exchange rate fluctuations. * @@ -334,7 +334,7 @@ export class OrderService { * @param userId - The ID of the user. * @returns A response containing the list of synthetic cash activities. */ - public async getCashOrders({ + public async getCashActivities({ cashDetails, filters = [], userCurrency, @@ -448,7 +448,10 @@ export class OrderService { }; } - public async getLatestOrder({ dataSource, symbol }: AssetProfileIdentifier) { + public async getLatestActivity({ + dataSource, + symbol + }: AssetProfileIdentifier) { return this.prismaService.order.findFirst({ orderBy: { date: 'desc' @@ -459,7 +462,7 @@ export class OrderService { }); } - public async getOrders({ + public async getActivities({ endDate, filters, includeDrafts = false, @@ -626,7 +629,7 @@ export class OrderService { orderBy = [{ [sortColumn]: sortDirection }]; } - if (types) { + if (types?.length > 0) { where.type = { in: types }; } @@ -742,17 +745,17 @@ export class OrderService { } /** - * Retrieves all orders required for the portfolio calculator, including both standard asset orders - * and optional synthetic orders representing cash activities. + * Retrieves all activities required for the portfolio calculator, including both standard asset activities + * and optional synthetic activities representing cash activities. */ @LogPerformance - public async getOrdersForPortfolioCalculator({ + public async getActivitiesForPortfolioCalculator({ filters, userCurrency, userId, withCash = false }: { - /** Optional filters to apply to the orders. */ + /** Optional filters to apply to the activities. */ filters?: Filter[]; /** The base currency of the user. */ userCurrency: string; @@ -761,7 +764,7 @@ export class OrderService { /** Whether to include cash activities in the result. */ withCash?: boolean; }) { - const orders = await this.getOrders({ + const activities = await this.getActivities({ filters, userCurrency, userId, @@ -775,18 +778,18 @@ export class OrderService { currency: userCurrency }); - const cashOrders = await this.getCashOrders({ + const cashActivities = await this.getCashActivities({ cashDetails, filters, userCurrency, userId }); - orders.activities.push(...cashOrders.activities); - orders.count += cashOrders.count; + activities.activities.push(...cashActivities.activities); + activities.count += cashActivities.count; } - return orders; + return activities; } public async getStatisticsByCurrency( @@ -817,7 +820,7 @@ export class OrderService { }); } - public async updateOrder({ + public async updateActivity({ data, where }: { @@ -882,7 +885,7 @@ export class OrderService { data: { tags: { set: [] } } }); - const order = await this.prismaService.order.update({ + const activity = await this.prismaService.order.update({ where, data: { ...data, @@ -896,11 +899,11 @@ export class OrderService { this.eventEmitter.emit( PortfolioChangedEvent.getName(), new PortfolioChangedEvent({ - userId: order.userId + userId: activity.userId }) ); - return order; + return activity; } private async orders(params: { diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index 24467c732..69b619625 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -172,7 +172,7 @@ export class AdminController { let date: Date; if (dateRange) { - const { startDate } = getIntervalFromDateRange(dateRange); + const { startDate } = getIntervalFromDateRange({ dateRange }); date = startDate; } @@ -247,14 +247,17 @@ export class AdminController { @Param('symbol') symbol: string ): Promise<{ price: number }> { try { - const price = await this.manualService.test(data.scraperConfiguration); + const price = await this.manualService.test({ + symbol, + scraperConfiguration: data.scraperConfiguration + }); if (price) { return { price }; } throw new Error( - `Could not parse the current market price for ${symbol} (${dataSource})` + `Could not parse the market price for ${symbol} (${dataSource})` ); } catch (error) { Logger.error(error, 'AdminController'); diff --git a/apps/api/src/app/admin/admin.module.ts b/apps/api/src/app/admin/admin.module.ts index 598b68f17..960a36629 100644 --- a/apps/api/src/app/admin/admin.module.ts +++ b/apps/api/src/app/admin/admin.module.ts @@ -1,4 +1,4 @@ -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module'; import { ApiModule } from '@ghostfolio/api/services/api/api.module'; import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module'; @@ -20,6 +20,7 @@ import { QueueModule } from './queue/queue.module'; @Module({ imports: [ + ActivitiesModule, ApiModule, BenchmarkModule, ConfigurationModule, @@ -28,7 +29,6 @@ import { QueueModule } from './queue/queue.module'; DemoModule, ExchangeRateDataModule, MarketDataModule, - OrderModule, PrismaModule, PropertyModule, QueueModule, diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index d1308d197..27abb6254 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -1,4 +1,4 @@ -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { environment } from '@ghostfolio/api/environments/environment'; import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; @@ -55,12 +55,12 @@ import { groupBy } from 'lodash'; @Injectable() export class AdminService { public constructor( + private readonly activitiesService: ActivitiesService, private readonly benchmarkService: BenchmarkService, private readonly configurationService: ConfigurationService, private readonly dataProviderService: DataProviderService, private readonly exchangeRateDataService: ExchangeRateDataService, private readonly marketDataService: MarketDataService, - private readonly orderService: OrderService, private readonly prismaService: PrismaService, private readonly propertyService: PropertyService, private readonly symbolProfileService: SymbolProfileService @@ -225,6 +225,10 @@ export class AdminService { presetId === 'ETF_WITHOUT_SECTORS' ) { filters = [{ id: 'ETF', type: 'ASSET_SUB_CLASS' }]; + } else if (presetId === 'NO_ACTIVITIES') { + where.activities = { + none: {} + }; } const searchQuery = filters.find(({ type }) => { @@ -466,10 +470,12 @@ 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)); + await this.activitiesService.getStatisticsByCurrency(currency)); } const [[assetProfile], marketData] = await Promise.all([ @@ -504,6 +510,8 @@ export class AdminService { dataSource, dateOfFirstActivity, symbol, + assetClass: isCurrencyAssetProfile ? AssetClass.LIQUIDITY : undefined, + assetSubClass: isCurrencyAssetProfile ? AssetSubClass.CASH : undefined, isActive: true } }; @@ -790,7 +798,7 @@ export class AdminService { if (isCurrency(getCurrencyFromSymbol(symbol))) { currency = getCurrencyFromSymbol(symbol); ({ activitiesCount, dateOfFirstActivity } = - await this.orderService.getStatisticsByCurrency(currency)); + await this.activitiesService.getStatisticsByCurrency(currency)); } const lastMarketPrice = lastMarketPriceMap.get( diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 89f52e1ea..8ebe05928 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -1,4 +1,5 @@ import { EventsModule } from '@ghostfolio/api/events/events.module'; +import { BullBoardAuthMiddleware } from '@ghostfolio/api/middlewares/bull-board-auth.middleware'; import { HtmlTemplateMiddleware } from '@ghostfolio/api/middlewares/html-template.middleware'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { CronModule } from '@ghostfolio/api/services/cron/cron.module'; @@ -10,10 +11,13 @@ import { PropertyModule } from '@ghostfolio/api/services/property/property.modul import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module'; import { + BULL_BOARD_ROUTE, DEFAULT_LANGUAGE_CODE, SUPPORTED_LANGUAGE_CODES } from '@ghostfolio/common/config'; +import { ExpressAdapter } from '@bull-board/express'; +import { BullBoardModule } from '@bull-board/nestjs'; import { BullModule } from '@nestjs/bull'; import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; @@ -25,6 +29,7 @@ import { join } from 'node:path'; import { AccessModule } from './access/access.module'; import { AccountModule } from './account/account.module'; +import { ActivitiesModule } from './activities/activities.module'; import { AdminModule } from './admin/admin.module'; import { AppController } from './app.controller'; import { AssetModule } from './asset/asset.module'; @@ -48,7 +53,6 @@ import { HealthModule } from './health/health.module'; import { ImportModule } from './import/import.module'; import { InfoModule } from './info/info.module'; import { LogoModule } from './logo/logo.module'; -import { OrderModule } from './order/order.module'; import { PlatformModule } from './platform/platform.module'; import { PortfolioModule } from './portfolio/portfolio.module'; import { RedisCacheModule } from './redis-cache/redis-cache.module'; @@ -62,6 +66,7 @@ import { UserModule } from './user/user.module'; AdminModule, AccessModule, AccountModule, + ActivitiesModule, AiModule, ApiKeysModule, AssetModule, @@ -69,6 +74,29 @@ import { UserModule } from './user/user.module'; AuthDeviceModule, AuthModule, BenchmarksModule, + ...(process.env.ENABLE_FEATURE_BULL_BOARD === 'true' + ? [ + BullBoardModule.forRoot({ + adapter: ExpressAdapter, + boardOptions: { + uiConfig: { + boardLogo: { + height: 0, + path: '', + width: 0 + }, + boardTitle: 'Job Queues', + favIcon: { + alternative: '/assets/favicon-32x32.png', + default: '/assets/favicon-32x32.png' + } + } + }, + middleware: BullBoardAuthMiddleware, + route: BULL_BOARD_ROUTE + }) + ] + : []), BullModule.forRoot({ redis: { db: parseInt(process.env.REDIS_DB ?? '0', 10), @@ -94,7 +122,6 @@ import { UserModule } from './user/user.module'; InfoModule, LogoModule, MarketDataModule, - OrderModule, PlatformModule, PlatformsModule, PortfolioModule, @@ -105,7 +132,12 @@ import { UserModule } from './user/user.module'; RedisCacheModule, ScheduleModule.forRoot(), ServeStaticModule.forRoot({ - exclude: ['/.well-known/*wildcard', '/api/*wildcard', '/sitemap.xml'], + exclude: [ + `${BULL_BOARD_ROUTE}/*wildcard`, + '/.well-known/*wildcard', + '/api/*wildcard', + '/sitemap.xml' + ], rootPath: join(__dirname, '..', 'client'), serveStaticOptions: { setHeaders: (res) => { diff --git a/apps/api/src/app/auth-device/auth-device.controller.ts b/apps/api/src/app/auth-device/auth-device.controller.ts index 15e853465..c46589d74 100644 --- a/apps/api/src/app/auth-device/auth-device.controller.ts +++ b/apps/api/src/app/auth-device/auth-device.controller.ts @@ -2,18 +2,43 @@ import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.s import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { permissions } from '@ghostfolio/common/permissions'; +import { RequestWithUser } from '@ghostfolio/common/types'; -import { Controller, Delete, Param, UseGuards } from '@nestjs/common'; +import { + Controller, + Delete, + HttpException, + Inject, + Param, + UseGuards +} from '@nestjs/common'; +import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; +import { getReasonPhrase, StatusCodes } from 'http-status-codes'; @Controller('auth-device') export class AuthDeviceController { - public constructor(private readonly authDeviceService: AuthDeviceService) {} + public constructor( + private readonly authDeviceService: AuthDeviceService, + @Inject(REQUEST) private readonly request: RequestWithUser + ) {} @Delete(':id') @HasPermission(permissions.deleteAuthDevice) @UseGuards(AuthGuard('jwt'), HasPermissionGuard) public async deleteAuthDevice(@Param('id') id: string): Promise { + const originalAuthDevice = await this.authDeviceService.authDevice({ + id, + userId: this.request.user.id + }); + + if (!originalAuthDevice) { + throw new HttpException( + getReasonPhrase(StatusCodes.FORBIDDEN), + StatusCodes.FORBIDDEN + ); + } + await this.authDeviceService.deleteAuthDevice({ id }); } } diff --git a/apps/api/src/app/endpoints/ai/ai.module.ts b/apps/api/src/app/endpoints/ai/ai.module.ts index 8a441fde7..eab4ecf8b 100644 --- a/apps/api/src/app/endpoints/ai/ai.module.ts +++ b/apps/api/src/app/endpoints/ai/ai.module.ts @@ -1,6 +1,6 @@ import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; @@ -29,6 +29,7 @@ import { AiService } from './ai.service'; @Module({ controllers: [AiController], imports: [ + ActivitiesModule, ApiModule, BenchmarkModule, ConfigurationModule, @@ -37,7 +38,6 @@ import { AiService } from './ai.service'; I18nModule, ImpersonationModule, MarketDataModule, - OrderModule, PortfolioSnapshotQueueModule, PrismaModule, PropertyModule, diff --git a/apps/api/src/app/endpoints/assets/assets.controller.ts b/apps/api/src/app/endpoints/assets/assets.controller.ts index a314b3f19..397686d8c 100644 --- a/apps/api/src/app/endpoints/assets/assets.controller.ts +++ b/apps/api/src/app/endpoints/assets/assets.controller.ts @@ -4,6 +4,7 @@ import { interpolate } from '@ghostfolio/common/helper'; import { Controller, Get, + OnModuleInit, Param, Res, Version, @@ -14,12 +15,14 @@ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; @Controller('assets') -export class AssetsController { +export class AssetsController implements OnModuleInit { private webManifest = ''; public constructor( public readonly configurationService: ConfigurationService - ) { + ) {} + + public onModuleInit() { try { this.webManifest = readFileSync( join(__dirname, 'assets', 'site.webmanifest'), diff --git a/apps/api/src/app/endpoints/benchmarks/benchmarks.controller.ts b/apps/api/src/app/endpoints/benchmarks/benchmarks.controller.ts index 629d90928..970925777 100644 --- a/apps/api/src/app/endpoints/benchmarks/benchmarks.controller.ts +++ b/apps/api/src/app/endpoints/benchmarks/benchmarks.controller.ts @@ -126,10 +126,10 @@ export class BenchmarksController { @Query('tags') filterByTags?: string, @Query('withExcludedAccounts') withExcludedAccountsParam = 'false' ): Promise { - const { endDate, startDate } = getIntervalFromDateRange( + const { endDate, startDate } = getIntervalFromDateRange({ dateRange, - new Date(startDateString) - ); + startDate: new Date(startDateString) + }); const filters = this.apiService.buildFiltersFromQueryParams({ filterByAccounts, diff --git a/apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts b/apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts index 8bdf79035..2bcd6177d 100644 --- a/apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts +++ b/apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts @@ -1,6 +1,6 @@ import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; @@ -32,6 +32,7 @@ import { BenchmarksService } from './benchmarks.service'; @Module({ controllers: [BenchmarksController], imports: [ + ActivitiesModule, ApiModule, ConfigurationModule, DataProviderModule, @@ -39,7 +40,6 @@ import { BenchmarksService } from './benchmarks.service'; I18nModule, ImpersonationModule, MarketDataModule, - OrderModule, PortfolioSnapshotQueueModule, PrismaModule, PropertyModule, diff --git a/apps/api/src/app/endpoints/public/public.controller.ts b/apps/api/src/app/endpoints/public/public.controller.ts index b4ecd37ba..b97640cab 100644 --- a/apps/api/src/app/endpoints/public/public.controller.ts +++ b/apps/api/src/app/endpoints/public/public.controller.ts @@ -1,5 +1,5 @@ import { AccessService } from '@ghostfolio/api/app/access/access.service'; -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; import { UserService } from '@ghostfolio/api/app/user/user.service'; import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor'; @@ -28,9 +28,9 @@ import { StatusCodes, getReasonPhrase } from 'http-status-codes'; export class PublicController { public constructor( private readonly accessService: AccessService, + private readonly activitiesService: ActivitiesService, private readonly configurationService: ConfigurationService, private readonly exchangeRateDataService: ExchangeRateDataService, - private readonly orderService: OrderService, private readonly portfolioService: PortfolioService, @Inject(REQUEST) private readonly request: RequestWithUser, private readonly userService: UserService @@ -81,7 +81,7 @@ export class PublicController { }) ]); - const { activities } = await this.orderService.getOrders({ + const { activities } = await this.activitiesService.getActivities({ sortColumn: 'date', sortDirection: 'desc', take: 10, @@ -167,6 +167,7 @@ export class PublicController { allocationInPercentage: portfolioPosition.valueInBaseCurrency / totalValue, assetClass: hasDetails ? portfolioPosition.assetClass : undefined, + assetProfile: hasDetails ? portfolioPosition.assetProfile : undefined, countries: hasDetails ? portfolioPosition.countries : [], currency: hasDetails ? portfolioPosition.currency : undefined, dataSource: portfolioPosition.dataSource, diff --git a/apps/api/src/app/endpoints/public/public.module.ts b/apps/api/src/app/endpoints/public/public.module.ts index 19e281dde..e8395228f 100644 --- a/apps/api/src/app/endpoints/public/public.module.ts +++ b/apps/api/src/app/endpoints/public/public.module.ts @@ -1,7 +1,7 @@ import { AccessModule } from '@ghostfolio/api/app/access/access.module'; import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; @@ -27,13 +27,13 @@ import { PublicController } from './public.controller'; controllers: [PublicController], imports: [ AccessModule, + ActivitiesModule, BenchmarkModule, DataProviderModule, ExchangeRateDataModule, I18nModule, ImpersonationModule, MarketDataModule, - OrderModule, PortfolioSnapshotQueueModule, PrismaModule, RedisCacheModule, diff --git a/apps/api/src/app/export/export.controller.ts b/apps/api/src/app/export/export.controller.ts index 6fda8f17f..4f4f4e6dd 100644 --- a/apps/api/src/app/export/export.controller.ts +++ b/apps/api/src/app/export/export.controller.ts @@ -15,6 +15,7 @@ import { } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; +import { Type as ActivityType } from '@prisma/client'; import { ExportService } from './export.service'; @@ -33,12 +34,15 @@ export class ExportController { public async export( @Query('accounts') filterByAccounts?: string, @Query('activityIds') filterByActivityIds?: string, + @Query('activityTypes') filterByTypes?: string, @Query('assetClasses') filterByAssetClasses?: string, @Query('dataSource') filterByDataSource?: string, @Query('symbol') filterBySymbol?: string, @Query('tags') filterByTags?: string ): Promise { const activityIds = filterByActivityIds?.split(',') ?? []; + const activityTypes = (filterByTypes?.split(',') as ActivityType[]) ?? []; + const filters = this.apiService.buildFiltersFromQueryParams({ filterByAccounts, filterByAssetClasses, @@ -49,6 +53,7 @@ export class ExportController { return this.exportService.export({ activityIds, + activityTypes, filters, userId: this.request.user.id, userSettings: this.request.user.settings.settings diff --git a/apps/api/src/app/export/export.module.ts b/apps/api/src/app/export/export.module.ts index 4f40cc417..6158fe043 100644 --- a/apps/api/src/app/export/export.module.ts +++ b/apps/api/src/app/export/export.module.ts @@ -1,5 +1,5 @@ import { AccountModule } from '@ghostfolio/api/app/account/account.module'; -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module'; import { ApiModule } from '@ghostfolio/api/services/api/api.module'; import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module'; @@ -14,9 +14,9 @@ import { ExportService } from './export.service'; controllers: [ExportController], imports: [ AccountModule, + ActivitiesModule, ApiModule, MarketDataModule, - OrderModule, TagModule, TransformDataSourceInRequestModule ], diff --git a/apps/api/src/app/export/export.service.ts b/apps/api/src/app/export/export.service.ts index d07b199be..4da942cd7 100644 --- a/apps/api/src/app/export/export.service.ts +++ b/apps/api/src/app/export/export.service.ts @@ -1,5 +1,5 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { environment } from '@ghostfolio/api/environments/environment'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { TagService } from '@ghostfolio/api/services/tag/tag.service'; @@ -10,25 +10,27 @@ import { } from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; -import { Platform, Prisma } from '@prisma/client'; +import { Platform, Prisma, Type as ActivityType } from '@prisma/client'; import { groupBy, uniqBy } from 'lodash'; @Injectable() export class ExportService { public constructor( private readonly accountService: AccountService, + private readonly activitiesService: ActivitiesService, private readonly marketDataService: MarketDataService, - private readonly orderService: OrderService, private readonly tagService: TagService ) {} public async export({ activityIds, + activityTypes, filters, userId, userSettings }: { activityIds?: string[]; + activityTypes?: ActivityType[]; filters?: Filter[]; userId: string; userSettings: UserSettings; @@ -38,12 +40,13 @@ export class ExportService { }); const platformsMap: { [platformId: string]: Platform } = {}; - let { activities } = await this.orderService.getOrders({ + let { activities } = await this.activitiesService.getActivities({ filters, userId, includeDrafts: true, sortColumn: 'date', sortDirection: 'asc', + types: activityTypes, userCurrency: userSettings?.baseCurrency, withExcludedAccountsAndActivities: true }); @@ -182,10 +185,8 @@ export class ExportService { isActive, isin, name, - scraperConfiguration, sectors, symbol, - symbolMapping, url }) => { return { @@ -204,11 +205,8 @@ export class ExportService { isin, marketData: marketDataByAssetProfile[id], name, - scraperConfiguration: - scraperConfiguration as unknown as Prisma.JsonArray, sectors: sectors as unknown as Prisma.JsonArray, symbol, - symbolMapping, url }; } diff --git a/apps/api/src/app/import/import.controller.ts b/apps/api/src/app/import/import.controller.ts index 81481fd65..d5724bef2 100644 --- a/apps/api/src/app/import/import.controller.ts +++ b/apps/api/src/app/import/import.controller.ts @@ -38,7 +38,7 @@ export class ImportController { @Post() @UseGuards(AuthGuard('jwt'), HasPermissionGuard) - @HasPermission(permissions.createOrder) + @HasPermission(permissions.createActivity) @UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) public async import( diff --git a/apps/api/src/app/import/import.module.ts b/apps/api/src/app/import/import.module.ts index a4a13f941..ca9b5667b 100644 --- a/apps/api/src/app/import/import.module.ts +++ b/apps/api/src/app/import/import.module.ts @@ -1,6 +1,6 @@ import { AccountModule } from '@ghostfolio/api/app/account/account.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { CacheModule } from '@ghostfolio/api/app/cache/cache.module'; -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; import { PlatformModule } from '@ghostfolio/api/app/platform/platform.module'; import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; @@ -25,6 +25,7 @@ import { ImportService } from './import.service'; controllers: [ImportController], imports: [ AccountModule, + ActivitiesModule, ApiModule, CacheModule, ConfigurationModule, @@ -32,7 +33,6 @@ import { ImportService } from './import.service'; DataProviderModule, ExchangeRateDataModule, MarketDataModule, - OrderModule, PlatformModule, PortfolioModule, PrismaModule, diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 7e8e333b9..b82f763a0 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -1,10 +1,10 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { PlatformService } from '@ghostfolio/api/app/platform/platform.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; import { ApiService } from '@ghostfolio/api/services/api/api.service'; -import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; +import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; @@ -32,7 +32,7 @@ import { } from '@ghostfolio/common/types'; import { Injectable } from '@nestjs/common'; -import { DataSource, Prisma, SymbolProfile } from '@prisma/client'; +import { DataSource, Prisma } from '@prisma/client'; import { Big } from 'big.js'; import { endOfToday, isAfter, isSameSecond, parseISO } from 'date-fns'; import { omit, uniqBy } from 'lodash'; @@ -44,12 +44,12 @@ import { ImportDataDto } from './import-data.dto'; export class ImportService { public constructor( private readonly accountService: AccountService, + private readonly activitiesService: ActivitiesService, private readonly apiService: ApiService, - private readonly configurationService: ConfigurationService, private readonly dataGatheringService: DataGatheringService, private readonly dataProviderService: DataProviderService, + private readonly exchangeRateDataService: ExchangeRateDataService, private readonly marketDataService: MarketDataService, - private readonly orderService: OrderService, private readonly platformService: PlatformService, private readonly portfolioService: PortfolioService, private readonly symbolProfileService: SymbolProfileService, @@ -91,7 +91,7 @@ export class ImportService { userId, withExcludedAccounts: true }), - this.orderService.getOrders({ + this.activitiesService.getActivities({ filters, userCurrency, userId, @@ -393,7 +393,7 @@ export class ImportService { } } - const assetProfiles = await this.validateActivities({ + const assetProfiles = await this.dataProviderService.validateActivities({ activitiesDto, assetProfilesWithMarketDataDto, maxActivitiesToImport, @@ -548,7 +548,7 @@ export class ImportService { continue; } - order = await this.orderService.createOrder({ + order = await this.activitiesService.createActivity({ comment, currency, date, @@ -590,10 +590,18 @@ export class ImportService { const value = new Big(quantity).mul(unitPrice).toNumber(); + const valueInBaseCurrency = this.exchangeRateDataService.toCurrencyAtDate( + value, + currency ?? assetProfile.currency, + userCurrency, + date + ); + activities.push({ ...order, error, value, + valueInBaseCurrency: await valueInBaseCurrency, // @ts-ignore SymbolProfile: assetProfile }); @@ -637,7 +645,7 @@ export class ImportService { userId: string; }): Promise[]> { const { activities: existingActivities } = - await this.orderService.getOrders({ + await this.activitiesService.getActivities({ userCurrency, userId, includeDrafts: true, @@ -719,132 +727,4 @@ export class ImportService { return uniqueAccountIds.size === 1; } - - private async validateActivities({ - activitiesDto, - assetProfilesWithMarketDataDto, - maxActivitiesToImport, - user - }: { - activitiesDto: Partial[]; - assetProfilesWithMarketDataDto: ImportDataDto['assetProfiles']; - maxActivitiesToImport: number; - user: UserWithSettings; - }) { - if (activitiesDto?.length > maxActivitiesToImport) { - throw new Error(`Too many activities (${maxActivitiesToImport} at most)`); - } - - const assetProfiles: { - [assetProfileIdentifier: string]: Partial; - } = {}; - const dataSources = await this.dataProviderService.getDataSources(); - - for (const [ - index, - { currency, dataSource, symbol, type } - ] of activitiesDto.entries()) { - if (!dataSources.includes(dataSource)) { - throw new Error( - `activities.${index}.dataSource ("${dataSource}") is not valid` - ); - } - - if ( - this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && - user.subscription.type === 'Basic' - ) { - const dataProvider = this.dataProviderService.getDataProvider( - DataSource[dataSource] - ); - - if (dataProvider.getDataProviderInfo().isPremium) { - throw new Error( - `activities.${index}.dataSource ("${dataSource}") is not valid` - ); - } - } - - if (!assetProfiles[getAssetProfileIdentifier({ dataSource, symbol })]) { - if (['FEE', 'INTEREST', 'LIABILITY'].includes(type)) { - // Skip asset profile validation for FEE, INTEREST, and LIABILITY - // as these activity types don't require asset profiles - const assetProfileInImport = assetProfilesWithMarketDataDto?.find( - (profile) => { - return ( - profile.dataSource === dataSource && profile.symbol === symbol - ); - } - ); - - assetProfiles[getAssetProfileIdentifier({ dataSource, symbol })] = { - currency, - dataSource, - symbol, - name: assetProfileInImport?.name - }; - - continue; - } - - let assetProfile: Partial = { currency }; - - try { - assetProfile = ( - await this.dataProviderService.getAssetProfiles([ - { dataSource, symbol } - ]) - )?.[symbol]; - } catch {} - - if (!assetProfile?.name) { - const assetProfileInImport = assetProfilesWithMarketDataDto?.find( - (profile) => { - return ( - profile.dataSource === dataSource && profile.symbol === symbol - ); - } - ); - - if (assetProfileInImport) { - // Merge all fields of custom asset profiles into the validation object - Object.assign(assetProfile, { - assetClass: assetProfileInImport.assetClass, - assetSubClass: assetProfileInImport.assetSubClass, - comment: assetProfileInImport.comment, - countries: assetProfileInImport.countries, - currency: assetProfileInImport.currency, - cusip: assetProfileInImport.cusip, - dataSource: assetProfileInImport.dataSource, - figi: assetProfileInImport.figi, - figiComposite: assetProfileInImport.figiComposite, - figiShareClass: assetProfileInImport.figiShareClass, - holdings: assetProfileInImport.holdings, - isActive: assetProfileInImport.isActive, - isin: assetProfileInImport.isin, - name: assetProfileInImport.name, - scraperConfiguration: assetProfileInImport.scraperConfiguration, - sectors: assetProfileInImport.sectors, - symbol: assetProfileInImport.symbol, - symbolMapping: assetProfileInImport.symbolMapping, - url: assetProfileInImport.url - }); - } - } - - if (!['FEE', 'INTEREST', 'LIABILITY'].includes(type)) { - if (!assetProfile?.name) { - throw new Error( - `activities.${index}.symbol ("${symbol}") is not valid for the specified data source ("${dataSource}")` - ); - } - } - - assetProfiles[getAssetProfileIdentifier({ dataSource, symbol })] = - assetProfile; - } - } - - return assetProfiles; - } } diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index 2e58a4ef5..d57b85d8c 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -53,6 +53,7 @@ import { isBefore, isWithinInterval, min, + startOfDay, startOfYear, subDays } from 'date-fns'; @@ -157,13 +158,13 @@ export abstract class PortfolioCalculator { this.redisCacheService = redisCacheService; this.userId = userId; - const { endDate, startDate } = getIntervalFromDateRange( - 'max', - subDays(dateOfFirstActivity, 1) - ); + const { endDate, startDate } = getIntervalFromDateRange({ + dateRange: 'max', + startDate: subDays(dateOfFirstActivity, 1) + }); - this.endDate = endDate; - this.startDate = startDate; + this.endDate = endOfDay(endDate); + this.startDate = startOfDay(startDate); this.computeTransactionPoints(); @@ -236,7 +237,7 @@ export abstract class PortfolioCalculator { const exchangeRatesByCurrency = await this.exchangeRateDataService.getExchangeRatesByCurrency({ currencies: Array.from(new Set(Object.values(currencies))), - endDate: endOfDay(this.endDate), + endDate: this.endDate, startDate: this.startDate, targetCurrency: this.currency }); @@ -884,7 +885,7 @@ export abstract class PortfolioCalculator { // Make sure some key dates are present for (const dateRange of ['1d', '1y', '5y', 'max', 'mtd', 'wtd', 'ytd']) { const { endDate: dateRangeEnd, startDate: dateRangeStart } = - getIntervalFromDateRange(dateRange); + getIntervalFromDateRange({ dateRange }); if ( !isBefore(dateRangeStart, startDate) && diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts index 52c8489dd..9a93d0419 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts @@ -194,6 +194,7 @@ describe('PortfolioCalculator', () => { netPerformanceInPercentage: 0.07032490039195362, netPerformanceInPercentageWithCurrencyEffect: 0.07032490039195362, netPerformanceWithCurrencyEffect: 33.4, + totalInvestment: 559, totalInvestmentValueWithCurrencyEffect: 559 }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts index 3998b081d..c876d0db1 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts @@ -208,6 +208,7 @@ describe('PortfolioCalculator', () => { netPerformanceInPercentage: -0.05528341497550734703, netPerformanceInPercentageWithCurrencyEffect: -0.05528341497550734703, netPerformanceWithCurrencyEffect: -15.8, + totalInvestment: 0, totalInvestmentValueWithCurrencyEffect: 0 }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts index acd0d0b2e..ae921d6d9 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts @@ -192,6 +192,7 @@ describe('PortfolioCalculator', () => { netPerformanceInPercentage: -0.05528341497550734703, netPerformanceInPercentageWithCurrencyEffect: -0.05528341497550734703, netPerformanceWithCurrencyEffect: -15.8, + totalInvestment: 0, totalInvestmentValueWithCurrencyEffect: 0 }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts index 652e72db0..6207f1417 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts @@ -192,6 +192,7 @@ describe('PortfolioCalculator', () => { netPerformanceInPercentage: 0.08437042459736457, netPerformanceInPercentageWithCurrencyEffect: 0.08437042459736457, netPerformanceWithCurrencyEffect: 23.05, + totalInvestment: 273.2, totalInvestmentValueWithCurrencyEffect: 273.2 }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts index a70cc2986..11765fc49 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts @@ -210,6 +210,7 @@ describe('PortfolioCalculator', () => { netPerformanceInPercentage: 42.41983590271396609433, netPerformanceInPercentageWithCurrencyEffect: 41.64017412624815597854, netPerformanceWithCurrencyEffect: 26516.208701400000064086, + totalInvestment: 318.542667299999967957, totalInvestmentValueWithCurrencyEffect: 318.542667299999967957 }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts index a53ebcf05..217a67c49 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts @@ -1,6 +1,6 @@ import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; @@ -62,11 +62,11 @@ jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { describe('PortfolioCalculator', () => { let accountBalanceService: AccountBalanceService; let accountService: AccountService; + let activitiesService: ActivitiesService; let configurationService: ConfigurationService; let currentRateService: CurrentRateService; let dataProviderService: DataProviderService; let exchangeRateDataService: ExchangeRateDataService; - let orderService: OrderService; let portfolioCalculatorFactory: PortfolioCalculatorFactory; let portfolioSnapshotService: PortfolioSnapshotService; let redisCacheService: RedisCacheService; @@ -106,13 +106,13 @@ describe('PortfolioCalculator', () => { ); currentRateService = new CurrentRateService( - dataProviderService, null, + dataProviderService, null, null ); - orderService = new OrderService( + activitiesService = new ActivitiesService( accountBalanceService, accountService, null, @@ -183,18 +183,17 @@ describe('PortfolioCalculator', () => { .spyOn(dataProviderService, 'getDataSourceForExchangeRates') .mockReturnValue(DataSource.YAHOO); - jest.spyOn(orderService, 'getOrders').mockResolvedValue({ + jest.spyOn(activitiesService, 'getActivities').mockResolvedValue({ activities: [], count: 0 }); - const { activities } = await orderService.getOrdersForPortfolioCalculator( - { + const { activities } = + await activitiesService.getActivitiesForPortfolioCalculator({ userCurrency: 'CHF', userId: userDummyData.id, withCash: true - } - ); + }); jest.spyOn(currentRateService, 'getValues').mockResolvedValue({ dataProviderInfos: [], diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts index 59a5531df..a3fbc0758 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts @@ -128,6 +128,7 @@ describe('PortfolioCalculator', () => { netPerformanceInPercentage: 0, netPerformanceInPercentageWithCurrencyEffect: 0, netPerformanceWithCurrencyEffect: 0, + totalInvestment: 0, totalInvestmentValueWithCurrencyEffect: 0 }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts index 9b48a1324..122a9aaed 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts @@ -188,6 +188,7 @@ describe('PortfolioCalculator', () => { netPerformanceInPercentage: 0.29544434470377019749, netPerformanceInPercentageWithCurrencyEffect: 0.24112962014285697628, netPerformanceWithCurrencyEffect: 19.851974, + totalInvestment: new Big('89.12').mul(0.8854).toNumber(), totalInvestmentValueWithCurrencyEffect: 82.329056 }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts new file mode 100644 index 000000000..d5b22e864 --- /dev/null +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts @@ -0,0 +1,190 @@ +import { + activityDummyData, + loadExportFile, + symbolProfileDummyData, + userDummyData +} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils'; +import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; +import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; +import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock'; +import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; +import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; +import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; +import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock'; +import { parseDate } from '@ghostfolio/common/helper'; +import { Activity, ExportResponse } from '@ghostfolio/common/interfaces'; +import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type'; + +import { Big } from 'big.js'; +import { join } from 'node:path'; + +jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { + return { + CurrentRateService: jest.fn().mockImplementation(() => { + return CurrentRateServiceMock; + }) + }; +}); + +jest.mock( + '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service', + () => { + return { + PortfolioSnapshotService: jest.fn().mockImplementation(() => { + return PortfolioSnapshotServiceMock; + }) + }; + } +); + +jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { + return { + RedisCacheService: jest.fn().mockImplementation(() => { + return RedisCacheServiceMock; + }) + }; +}); + +describe('PortfolioCalculator', () => { + let exportResponse: ExportResponse; + + let configurationService: ConfigurationService; + let currentRateService: CurrentRateService; + let exchangeRateDataService: ExchangeRateDataService; + let portfolioCalculatorFactory: PortfolioCalculatorFactory; + let portfolioSnapshotService: PortfolioSnapshotService; + let redisCacheService: RedisCacheService; + + beforeAll(() => { + exportResponse = loadExportFile( + join( + __dirname, + '../../../../../../../test/import/ok/jnug-buy-and-sell-and-buy-and-sell.json' + ) + ); + }); + + beforeEach(() => { + configurationService = new ConfigurationService(); + + currentRateService = new CurrentRateService(null, null, null, null); + + exchangeRateDataService = new ExchangeRateDataService( + null, + null, + null, + null + ); + + portfolioSnapshotService = new PortfolioSnapshotService(null); + + redisCacheService = new RedisCacheService(null, null); + + portfolioCalculatorFactory = new PortfolioCalculatorFactory( + configurationService, + currentRateService, + exchangeRateDataService, + portfolioSnapshotService, + redisCacheService + ); + }); + + describe('get current positions', () => { + it.only('with JNUG buy and sell', async () => { + jest.useFakeTimers().setSystemTime(parseDate('2025-12-28').getTime()); + + const activities: Activity[] = exportResponse.activities.map( + (activity) => ({ + ...activityDummyData, + ...activity, + date: parseDate(activity.date), + feeInAssetProfileCurrency: activity.fee, + feeInBaseCurrency: activity.fee, + SymbolProfile: { + ...symbolProfileDummyData, + currency: activity.currency, + dataSource: activity.dataSource, + name: 'Direxion Daily Junior Gold Miners Index Bull 2X Shares', + symbol: activity.symbol + }, + unitPriceInAssetProfileCurrency: activity.unitPrice + }) + ); + + const portfolioCalculator = portfolioCalculatorFactory.createCalculator({ + activities, + calculationType: PerformanceCalculationType.ROAI, + currency: exportResponse.user.settings.currency, + userId: userDummyData.id + }); + + const portfolioSnapshot = await portfolioCalculator.computeSnapshot(); + + const investments = portfolioCalculator.getInvestments(); + + const investmentsByMonth = portfolioCalculator.getInvestmentsByGroup({ + data: portfolioSnapshot.historicalData, + groupBy: 'month' + }); + + const investmentsByYear = portfolioCalculator.getInvestmentsByGroup({ + data: portfolioSnapshot.historicalData, + groupBy: 'year' + }); + + expect(portfolioSnapshot).toMatchObject({ + currentValueInBaseCurrency: new Big('0'), + errors: [], + hasErrors: false, + positions: [ + { + activitiesCount: 4, + averagePrice: new Big('0'), + currency: 'USD', + dataSource: 'YAHOO', + dateOfFirstActivity: '2025-12-11', + dividend: new Big('0'), + dividendInBaseCurrency: new Big('0'), + fee: new Big('4'), + feeInBaseCurrency: new Big('4'), + grossPerformance: new Big('43.95'), // (1890.00 - 1885.05) + (2080.10 - 2041.10) + grossPerformanceWithCurrencyEffect: new Big('43.95'), // (1890.00 - 1885.05) + (2080.10 - 2041.10) + investment: new Big('0'), + investmentWithCurrencyEffect: new Big('0'), + netPerformance: new Big('39.95'), // (1890.00 - 1885.05) + (2080.10 - 2041.10) - 4 + netPerformanceWithCurrencyEffectMap: { + max: new Big('39.95') // (1890.00 - 1885.05) + (2080.10 - 2041.10) - 4 + }, + marketPrice: 237.8000030517578, + marketPriceInBaseCurrency: 237.8000030517578, + quantity: new Big('0'), + symbol: 'JNUG', + tags: [], + valueInBaseCurrency: new Big('0') + } + ], + totalFeesWithCurrencyEffect: new Big('4'), + totalInterestWithCurrencyEffect: new Big('0'), + totalInvestment: new Big('0'), + totalInvestmentWithCurrencyEffect: new Big('0'), + totalLiabilitiesWithCurrencyEffect: new Big('0') + }); + + expect(investments).toEqual([ + { date: '2025-12-11', investment: new Big('1885.05') }, + { date: '2025-12-18', investment: new Big('2041.1') }, + { date: '2025-12-28', investment: new Big('0') } + ]); + + expect(investmentsByMonth).toEqual([ + { date: '2025-12-01', investment: 0 } + ]); + + expect(investmentsByYear).toEqual([ + { date: '2025-01-01', investment: 0 } + ]); + }); + }); +}); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts index b19adb642..e7eff6682 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts @@ -174,6 +174,7 @@ describe('PortfolioCalculator', () => { expect(portfolioSnapshot.historicalData.at(-1)).toMatchObject( expect.objectContaining({ + totalInvestment: 298.58, totalInvestmentValueWithCurrencyEffect: 298.58 }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts index fecf17011..3034e3a1f 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts @@ -190,6 +190,7 @@ describe('PortfolioCalculator', () => { netPerformanceInPercentage: 0.12184460284330327256, netPerformanceInPercentageWithCurrencyEffect: 0.12184460284330327256, netPerformanceWithCurrencyEffect: 17.68, + totalInvestment: 75.8, totalInvestmentValueWithCurrencyEffect: 75.8 }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts index adbb5c3ff..c79fdef58 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts @@ -241,6 +241,7 @@ describe('PortfolioCalculator', () => { netPerformanceInPercentage: 0.13100263852242744063, netPerformanceInPercentageWithCurrencyEffect: 0.13100263852242744063, netPerformanceWithCurrencyEffect: 19.86, + totalInvestment: 0, totalInvestmentValueWithCurrencyEffect: 0 }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts index 6fc94622f..e518a5994 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts @@ -162,6 +162,7 @@ describe('PortfolioCalculator', () => { netPerformanceInPercentage: 0, netPerformanceInPercentageWithCurrencyEffect: 0, netPerformanceWithCurrencyEffect: 0, + totalInvestment: 500000, totalInvestmentValueWithCurrencyEffect: 500000 }) ); diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts index fe912510a..2841e9975 100644 --- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts @@ -626,6 +626,13 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { totalQuantityFromBuyTransactions ); + if (totalUnits.eq(0)) { + // Reset tracking variables when position is fully closed + totalInvestmentFromBuyTransactions = new Big(0); + totalInvestmentFromBuyTransactionsWithCurrencyEffect = new Big(0); + totalQuantityFromBuyTransactions = new Big(0); + } + if (PortfolioCalculator.ENABLE_LOGGING) { console.log( 'grossPerformanceFromSells', @@ -853,7 +860,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator { return format(date, 'yyyy'); }) ] as DateRange[]) { - const dateInterval = getIntervalFromDateRange(dateRange); + const dateInterval = getIntervalFromDateRange({ dateRange }); const endDate = dateInterval.endDate; let startDate = dateInterval.startDate; diff --git a/apps/api/src/app/portfolio/current-rate.service.mock.ts b/apps/api/src/app/portfolio/current-rate.service.mock.ts index 4b4b8f00e..8e027f971 100644 --- a/apps/api/src/app/portfolio/current-rate.service.mock.ts +++ b/apps/api/src/app/portfolio/current-rate.service.mock.ts @@ -64,6 +64,17 @@ function mockGetValue(symbol: string, date: Date) { return { marketPrice: 0 }; + case 'JNUG': + if (isSameDay(parseDate('2025-12-10'), date)) { + return { marketPrice: 204.5599975585938 }; + } else if (isSameDay(parseDate('2025-12-17'), date)) { + return { marketPrice: 203.9700012207031 }; + } else if (isSameDay(parseDate('2025-12-28'), date)) { + return { marketPrice: 237.8000030517578 }; + } + + return { marketPrice: 0 }; + case 'MSFT': if (isSameDay(parseDate('2021-09-16'), date)) { return { marketPrice: 89.12 }; diff --git a/apps/api/src/app/portfolio/current-rate.service.spec.ts b/apps/api/src/app/portfolio/current-rate.service.spec.ts index d8b7482e7..5f2358679 100644 --- a/apps/api/src/app/portfolio/current-rate.service.spec.ts +++ b/apps/api/src/app/portfolio/current-rate.service.spec.ts @@ -114,9 +114,9 @@ describe('CurrentRateService', () => { marketDataService = new MarketDataService(null); currentRateService = new CurrentRateService( + null, dataProviderService, marketDataService, - null, null ); }); diff --git a/apps/api/src/app/portfolio/current-rate.service.ts b/apps/api/src/app/portfolio/current-rate.service.ts index 5d39a54bb..b454b01cd 100644 --- a/apps/api/src/app/portfolio/current-rate.service.ts +++ b/apps/api/src/app/portfolio/current-rate.service.ts @@ -1,4 +1,4 @@ -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; @@ -24,9 +24,9 @@ export class CurrentRateService { private static readonly MARKET_DATA_PAGE_SIZE = 50000; public constructor( + private readonly activitiesService: ActivitiesService, private readonly dataProviderService: DataProviderService, private readonly marketDataService: MarketDataService, - private readonly orderService: OrderService, @Inject(REQUEST) private readonly request: RequestWithUser ) {} @@ -129,10 +129,11 @@ export class CurrentRateService { if (!value) { // Fallback to unit price of latest activity - const latestActivity = await this.orderService.getLatestOrder({ - dataSource, - symbol - }); + const latestActivity = + await this.activitiesService.getLatestActivity({ + dataSource, + symbol + }); value = { dataSource, diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index b8aefe0ac..9c41aecb9 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -1,4 +1,4 @@ -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { @@ -63,10 +63,10 @@ import { UpdateHoldingTagsDto } from './update-holding-tags.dto'; @Controller('portfolio') export class PortfolioController { public constructor( + private readonly activitiesService: ActivitiesService, private readonly apiService: ApiService, private readonly configurationService: ConfigurationService, private readonly impersonationService: ImpersonationService, - private readonly orderService: OrderService, private readonly portfolioService: PortfolioService, @Inject(REQUEST) private readonly request: RequestWithUser ) {} @@ -187,7 +187,6 @@ export class PortfolioController { portfolioSummary = nullifyValuesInObject(summary, [ 'cash', - 'committedFunds', 'currentNetWorth', 'currentValueInBaseCurrency', 'dividendInBaseCurrency', @@ -206,6 +205,7 @@ export class PortfolioController { 'netPerformanceWithCurrencyEffect', 'totalBuy', 'totalInvestment', + 'totalInvestmentValueWithCurrencyEffect', 'totalSell', 'totalValueInBaseCurrency' ]); @@ -320,9 +320,9 @@ export class PortfolioController { await this.impersonationService.validateImpersonationId(impersonationId); const userCurrency = this.request.user.settings.settings.baseCurrency; - const { endDate, startDate } = getIntervalFromDateRange(dateRange); + const { endDate, startDate } = getIntervalFromDateRange({ dateRange }); - const { activities } = await this.orderService.getOrders({ + const { activities } = await this.activitiesService.getActivities({ endDate, filters, startDate, @@ -639,7 +639,7 @@ export class PortfolioController { return report; } - @HasPermission(permissions.updateOrder) + @HasPermission(permissions.updateActivity) @Put('holding/:dataSource/:symbol/tags') @UseInterceptors(TransformDataSourceInRequestInterceptor) @UseGuards(AuthGuard('jwt'), HasPermissionGuard) diff --git a/apps/api/src/app/portfolio/portfolio.module.ts b/apps/api/src/app/portfolio/portfolio.module.ts index 6dd5811a3..65a9b71aa 100644 --- a/apps/api/src/app/portfolio/portfolio.module.ts +++ b/apps/api/src/app/portfolio/portfolio.module.ts @@ -1,7 +1,7 @@ import { AccessModule } from '@ghostfolio/api/app/access/access.module'; import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { UserModule } from '@ghostfolio/api/app/user/user.module'; import { PerformanceLoggingModule } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.module'; @@ -34,6 +34,7 @@ import { RulesService } from './rules.service'; exports: [PortfolioService], imports: [ AccessModule, + ActivitiesModule, ApiModule, BenchmarkModule, ConfigurationModule, @@ -43,7 +44,6 @@ import { RulesService } from './rules.service'; I18nModule, ImpersonationModule, MarketDataModule, - OrderModule, PerformanceLoggingModule, PortfolioSnapshotQueueModule, PrismaModule, diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 7be375473..60b413cf9 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -1,7 +1,7 @@ import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details.interface'; -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { UserService } from '@ghostfolio/api/app/user/user.service'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment'; @@ -13,7 +13,7 @@ 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'; import { RegionalMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/emerging-markets'; @@ -105,13 +105,13 @@ export class PortfolioService { public constructor( private readonly accountBalanceService: AccountBalanceService, private readonly accountService: AccountService, + private readonly activitiesService: ActivitiesService, private readonly benchmarkService: BenchmarkService, private readonly calculatorFactory: PortfolioCalculatorFactory, private readonly dataProviderService: DataProviderService, private readonly exchangeRateDataService: ExchangeRateDataService, private readonly i18nService: I18nService, private readonly impersonationService: ImpersonationService, - private readonly orderService: OrderService, @Inject(REQUEST) private readonly request: RequestWithUser, private readonly rulesService: RulesService, private readonly symbolProfileService: SymbolProfileService, @@ -403,10 +403,10 @@ export class PortfolioService { const user = await this.userService.user({ id: userId }); const userCurrency = this.getUserCurrency(user); - const { endDate, startDate } = getIntervalFromDateRange(dateRange); + const { endDate, startDate } = getIntervalFromDateRange({ dateRange }); const { activities } = - await this.orderService.getOrdersForPortfolioCalculator({ + await this.activitiesService.getActivitiesForPortfolioCalculator({ filters, userCurrency, userId @@ -490,7 +490,7 @@ export class PortfolioService { ); const { activities } = - await this.orderService.getOrdersForPortfolioCalculator({ + await this.activitiesService.getActivitiesForPortfolioCalculator({ filters, userCurrency, userId @@ -623,6 +623,28 @@ export class PortfolioService { ? 0 : valueInBaseCurrency.div(filteredValueInBaseCurrency).toNumber(), assetClass: assetProfile.assetClass, + assetProfile: { + assetClass: assetProfile.assetClass, + assetSubClass: assetProfile.assetSubClass, + countries: assetProfile.countries, + currency: assetProfile.currency, + dataSource: assetProfile.dataSource, + holdings: assetProfile.holdings.map( + ({ allocationInPercentage, name }) => { + return { + allocationInPercentage, + name, + valueInBaseCurrency: valueInBaseCurrency + .mul(allocationInPercentage) + .toNumber() + }; + } + ), + name: assetProfile.name, + sectors: assetProfile.sectors, + symbol: assetProfile.symbol, + url: assetProfile.url + }, assetSubClass: assetProfile.assetSubClass, countries: assetProfile.countries, dataSource: assetProfile.dataSource, @@ -758,7 +780,7 @@ export class PortfolioService { const userCurrency = this.getUserCurrency(user); const { activities } = - await this.orderService.getOrdersForPortfolioCalculator({ + await this.activitiesService.getActivitiesForPortfolioCalculator({ userCurrency, userId }); @@ -988,7 +1010,7 @@ export class PortfolioService { userId, userCurrency }), - this.orderService.getOrdersForPortfolioCalculator({ + this.activitiesService.getActivitiesForPortfolioCalculator({ filters, userCurrency, userId @@ -1007,7 +1029,8 @@ export class PortfolioService { netPerformancePercentage: 0, netPerformancePercentageWithCurrencyEffect: 0, netPerformanceWithCurrencyEffect: 0, - totalInvestment: 0 + totalInvestment: 0, + totalInvestmentValueWithCurrencyEffect: 0 } }; } @@ -1024,7 +1047,7 @@ export class PortfolioService { const { errors, hasErrors, historicalData } = await portfolioCalculator.getSnapshot(); - const { endDate, startDate } = getIntervalFromDateRange(dateRange); + const { endDate, startDate } = getIntervalFromDateRange({ dateRange }); const { chart } = await portfolioCalculator.getPerformance({ end: endDate, @@ -1038,6 +1061,7 @@ export class PortfolioService { netPerformanceWithCurrencyEffect, netWorth, totalInvestment, + totalInvestmentValueWithCurrencyEffect, valueWithCurrencyEffect } = chart?.at(-1) ?? { netPerformance: 0, @@ -1058,6 +1082,7 @@ export class PortfolioService { netPerformance, netPerformanceWithCurrencyEffect, totalInvestment, + totalInvestmentValueWithCurrencyEffect, currentNetWorth: netWorth, currentValueInBaseCurrency: valueWithCurrencyEffect, netPerformancePercentage: netPerformanceInPercentage, @@ -1306,11 +1331,11 @@ export class PortfolioService { }), rules: await this.rulesService.evaluate( [ - new FeeRatioInitialInvestment( + new FeeRatioTotalInvestmentVolume( this.exchangeRateDataService, this.i18nService, userSettings.language, - summary.committedFunds, + summary.totalBuy + summary.totalSell, summary.fees ) ], @@ -1346,7 +1371,12 @@ export class PortfolioService { }) { userId = await this.getUserId(impersonationId, userId); - await this.orderService.assignTags({ dataSource, symbol, tags, userId }); + await this.activitiesService.assignTags({ + dataSource, + symbol, + tags, + userId + }); } private getAggregatedMarkets(holdings: Record): { @@ -1669,6 +1699,17 @@ export class PortfolioService { allocationInPercentage: 0, assetClass: AssetClass.LIQUIDITY, assetSubClass: AssetSubClass.CASH, + assetProfile: { + currency, + assetClass: AssetClass.LIQUIDITY, + assetSubClass: AssetSubClass.CASH, + countries: [], + dataSource: undefined, + holdings: [], + name: currency, + sectors: [], + symbol: currency + }, countries: [], dataSource: undefined, dateOfFirstActivity: undefined, @@ -1838,7 +1879,7 @@ export class PortfolioService { userId = await this.getUserId(impersonationId, userId); const user = await this.userService.user({ id: userId }); - const { activities } = await this.orderService.getOrders({ + const { activities } = await this.activitiesService.getActivities({ userCurrency, userId, withExcludedAccountsAndActivities: true @@ -1860,8 +1901,11 @@ export class PortfolioService { } } - const { currentValueInBaseCurrency, totalInvestment } = - await portfolioCalculator.getSnapshot(); + const { + currentValueInBaseCurrency, + totalInvestment, + totalInvestmentWithCurrencyEffect + } = await portfolioCalculator.getSnapshot(); const { performance } = await this.getPerformance({ impersonationId, @@ -1908,8 +1952,6 @@ export class PortfolioService { .plus(emergencyFundHoldingsValueInBaseCurrency) .toNumber(); - const committedFunds = new Big(totalBuy).minus(totalSell); - const totalOfExcludedActivities = this.getSumOfActivityType({ userCurrency, activities: excludedActivities, @@ -1973,7 +2015,6 @@ export class PortfolioService { activityCount: activities.filter(({ type }) => { return ['BUY', 'SELL'].includes(type); }).length, - committedFunds: committedFunds.toNumber(), currentValueInBaseCurrency: currentValueInBaseCurrency.toNumber(), dividendInBaseCurrency: dividendInBaseCurrency.toNumber(), emergencyFund: { @@ -2004,6 +2045,8 @@ export class PortfolioService { interestInBaseCurrency: interest.toNumber(), liabilitiesInBaseCurrency: liabilities.toNumber(), totalInvestment: totalInvestment.toNumber(), + totalInvestmentValueWithCurrencyEffect: + totalInvestmentWithCurrencyEffect.toNumber(), totalValueInBaseCurrency: netWorth }; } diff --git a/apps/api/src/app/redis-cache/redis-cache.service.ts b/apps/api/src/app/redis-cache/redis-cache.service.ts index 1ea0a6137..619d23fc5 100644 --- a/apps/api/src/app/redis-cache/redis-cache.service.ts +++ b/apps/api/src/app/redis-cache/redis-cache.service.ts @@ -6,7 +6,7 @@ import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager'; import { Inject, Injectable, Logger } from '@nestjs/common'; import Keyv from 'keyv'; import ms from 'ms'; -import { createHash } from 'node:crypto'; +import { createHash, randomUUID } from 'node:crypto'; @Injectable() export class RedisCacheService { @@ -75,13 +75,16 @@ export class RedisCacheService { } public async isHealthy() { - const testKey = '__health_check__'; + const HEALTH_CHECK_TIMEOUT = ms('5 seconds'); + + const testKey = `__health_check__${randomUUID().replace(/-/g, '')}`; const testValue = Date.now().toString(); try { await Promise.race([ (async () => { - await this.set(testKey, testValue, ms('1 second')); + await this.set(testKey, testValue, HEALTH_CHECK_TIMEOUT); + const result = await this.get(testKey); if (result !== testValue) { @@ -91,7 +94,7 @@ export class RedisCacheService { new Promise((_, reject) => setTimeout( () => reject(new Error('Redis health check failed: timeout')), - ms('2 seconds') + HEALTH_CHECK_TIMEOUT ) ) ]); diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/app/subscription/subscription.service.ts index 689ee3e6a..877ea0ee4 100644 --- a/apps/api/src/app/subscription/subscription.service.ts +++ b/apps/api/src/app/subscription/subscription.service.ts @@ -35,7 +35,7 @@ export class SubscriptionService { this.stripe = new Stripe( this.configurationService.get('STRIPE_SECRET_KEY'), { - apiVersion: '2026-01-28.clover' + apiVersion: '2026-02-25.clover' } ); } diff --git a/apps/api/src/app/user/user.module.ts b/apps/api/src/app/user/user.module.ts index 7ca68d275..3f4e898fc 100644 --- a/apps/api/src/app/user/user.module.ts +++ b/apps/api/src/app/user/user.module.ts @@ -1,4 +1,4 @@ -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module'; import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; @@ -18,6 +18,7 @@ import { UserService } from './user.service'; controllers: [UserController], exports: [UserService], imports: [ + ActivitiesModule, ConfigurationModule, I18nModule, ImpersonationModule, @@ -25,7 +26,6 @@ import { UserService } from './user.service'; secret: process.env.JWT_SECRET_KEY, signOptions: { expiresIn: '30 days' } }), - OrderModule, PrismaModule, PropertyModule, RedactValuesInResponseModule, diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index def0b94d9..370f5d422 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -1,4 +1,4 @@ -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service'; import { environment } from '@ghostfolio/api/environments/environment'; import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event'; @@ -12,7 +12,7 @@ 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'; import { RegionalMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/emerging-markets'; @@ -55,10 +55,10 @@ import { createHmac } from 'node:crypto'; @Injectable() export class UserService { public constructor( + private readonly activitiesService: ActivitiesService, private readonly configurationService: ConfigurationService, private readonly eventEmitter: EventEmitter2, private readonly i18nService: I18nService, - private readonly orderService: OrderService, private readonly prismaService: PrismaService, private readonly propertyService: PropertyService, private readonly subscriptionService: SubscriptionService, @@ -376,7 +376,7 @@ export class UserService { undefined, undefined ).getSettings(user.settings.settings), - FeeRatioInitialInvestment: new FeeRatioInitialInvestment( + FeeRatioTotalInvestmentVolume: new FeeRatioTotalInvestmentVolume( undefined, undefined, undefined, @@ -530,8 +530,14 @@ export class UserService { } } - if (!environment.production && hasRole(user, Role.ADMIN)) { - currentPermissions.push(permissions.impersonateAllUsers); + if (hasRole(user, Role.ADMIN)) { + if (this.configurationService.get('ENABLE_FEATURE_BULL_BOARD')) { + currentPermissions.push(permissions.accessAdminControlBullBoard); + } + + if (!environment.production) { + currentPermissions.push(permissions.impersonateAllUsers); + } } user.accounts = user.accounts.sort((a, b) => { @@ -643,7 +649,7 @@ export class UserService { } catch {} try { - await this.orderService.deleteOrders({ + await this.activitiesService.deleteActivities({ userId: where.id }); } catch {} diff --git a/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json b/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json index e456f6f1d..96502ca05 100644 --- a/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json +++ b/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json @@ -421,6 +421,7 @@ "AGS": "Aegis", "AGT": "Alaya Governance Token", "AGURI": "Aguri-Chan", + "AGUSTO": "Agusto", "AGV": "Astra Guild Ventures", "AGVC": "AgaveCoin", "AGVE": "Agave", @@ -442,12 +443,14 @@ "AIAGENTAPP": "Aiagent.app", "AIAI": "All In AI", "AIAKITA": "AiAkita", + "AIAO": "AlgosOne AI Token", "AIAT": "AI Analysis Token", "AIAV": "AI AVatar", "AIB": "AdvancedInternetBlock", "AIBABYDOGE": "AIBabyDoge", "AIBB": "AiBB", "AIBCOIN": "AIBLOCK", + "AIBINANCE": "AI Binance", "AIBK": "AIB Utility Token", "AIBOT": "CherryAI", "AIBU": "AIBUZZ TOKEN", @@ -566,6 +569,7 @@ "AIX9": "AthenaX9", "AIXBT": "aixbt by Virtuals", "AIXCB": "aixCB by Virtuals", + "AIXDROP": "AIXDROP", "AIXERC": "AI-X", "AIXT": "AIXTerminal", "AJC": "AI Judge Companion", @@ -652,6 +656,7 @@ "ALLIN": "All in", "ALLMEE": "All.me", "ALLO": "Allora", + "ALLOCA": "Alloca", "ALM": "Alium Finance", "ALMAN": "Alman", "ALMANAK": "Almanak", @@ -662,6 +667,7 @@ "ALN": "Aluna", "ALNV1": "Aluna v1", "ALOHA": "Aloha", + "ALOKA": "ALOKA", "ALON": "Alon", "ALOR": "The Algorix", "ALOT": "Dexalot", @@ -674,6 +680,7 @@ "ALPHAAI": "Alpha AI", "ALPHABET": "Alphabet", "ALPHAC": "Alpha Coin", + "ALPHADEX": "Alpha DEX", "ALPHAF": "Alpha Fi", "ALPHAG": "Alpha Gardeners", "ALPHAPETTO": "Alpha Petto Shells", @@ -708,6 +715,7 @@ "AMADEUS": "AMADEUS", "AMAL": "AMAL", "AMAPT": "Amnis Finance", + "AMARA": "AMARA", "AMATEN": "Amaten", "AMATO": "AMATO", "AMAZINGTEAM": "AmazingTeamDAO", @@ -757,6 +765,7 @@ "AMX": "Amero", "AMY": "Amygws", "AMZE": "The Amaze World", + "AMZNON": "Amazon (Ondo Tokenized)", "AMZNX": "Amazon xStocks", "ANA": "Nirvana ANA", "ANAL": "AnalCoin", @@ -789,12 +798,13 @@ "ANGLE": "ANGLE", "ANGO": "Aureus Nummus Gold", "ANGRYSLERF": "ANGRYSLERF", - "ANI": "Anime Token", + "ANI": "Ani Grok Companion (anicompanion.net)", "ANIM": "Animalia", "ANIMA": "Realm Anima", "ANIME": "Animecoin", "ANIMECOIN": "Animecoin", "ANIMEONBASE": "Anime", + "ANIMETOKEN": "Anime Token (animetoken.in)", "ANITA": "Anita AI", "ANJ": "Aragon Court", "ANJI": "Anji", @@ -836,6 +846,7 @@ "ANVL": "Anvil", "ANVLV1": "Anvil v1", "ANW": "Anchor Neural World", + "ANWANG": "SAFE", "ANY": "Anyswap", "ANYONE": "ANyONe Protocol", "ANZENUSD": "Anzen Finance", @@ -1027,6 +1038,7 @@ "ARRO": "Arro Social", "ARROW": "Arrow Token", "ARRR": "Pirate Chain", + "ARSE": "ARSe", "ARSL": "Aquarius Loan", "ARSW": "ArthSwap", "ART": "LiveArt", @@ -1114,6 +1126,7 @@ "ASTONV": "Aston Villa Fan Token", "ASTR": "Astar", "ASTRA": "Astra Protocol", + "ASTRAAI": "AstraAI", "ASTRADAO": "Astra DAO", "ASTRAFER": "Astrafer", "ASTRAFERV1": "Astrafer v1", @@ -1213,11 +1226,12 @@ "AUN": "Authoreon", "AUNIT": "Aunit", "AUPC": "Authpaper", - "AUR": "AUREO", + "AUR": "Aurix", "AURA": "aura", "AURABAL": "Aura BAL", "AURAF": "Aura Finance", "AURANET": "Aura Network", + "AUREO": "AUREO", "AURO": "Aurora", "AURORA": "Aurora", "AURORAC": "Auroracoin", @@ -1274,6 +1288,7 @@ "AVINOC": "AVINOC", "AVIVE": "Avive World", "AVL": "AVL", + "AVLT": "Altura Vault Tokens", "AVM": "AVM (Atomicals)", "AVME": "AVME", "AVN": "AVNRich", @@ -1344,6 +1359,7 @@ "AZIT": "Azit", "AZNX": "AstraZeneca xStock", "AZR": "Azure", + "AZTEC": "AZTEC", "AZU": "Azultec", "AZUKI": "Azuki", "AZUKI2": "AZUKI 2.0", @@ -1373,6 +1389,7 @@ "BABI": "Babylons", "BABL": "Babylon Finance", "BABY": "Babylon", + "BABY4": "Baby 4", "BABYANDY": "Baby Andy", "BABYASTER": "Baby Aster", "BABYB": "Baby Bali", @@ -1415,6 +1432,7 @@ "BABYFB": "Baby Floki Billionaire", "BABYFLOKI": "BabyFloki", "BABYFLOKIZILLA": "BabyFlokiZilla", + "BABYFROG": "Baby Frog Coin", "BABYG": "BabyGME", "BABYGME": "Baby GameStop", "BABYGOAT": "Baby Goat", @@ -1571,7 +1589,7 @@ "BAOV1": "BaoToken v1", "BAP3X": "bAP3X", "BAR": "FC Barcelona Fan Token", - "BARA": "Capybara", + "BARA": "Capybara Nation", "BARAKATUH": "Barakatuh", "BARC": "The Blu Arctic Water Company", "BARD": "Lombard", @@ -1584,7 +1602,6 @@ "BART": "BarterTrade", "BARTKRC": "BART Token", "BARY": "Bary", - "BAS": "Basis Share", "BASEAI": "BaseAI", "BASEBEAR": "BBQ", "BASECAT": "BASE CAT", @@ -1600,6 +1617,7 @@ "BASEDP": "Based Pepe", "BASEDR": "Based Rabbit", "BASEDS": "BasedSwap", + "BASEDSB": "Based Street Bets", "BASEDTURBO": "Based Turbo", "BASEDV1": "Based Money v1", "BASEHEROES": "Baseheroes", @@ -1615,6 +1633,8 @@ "BASIL": "Basilisk", "BASIS": "Basis", "BASISCOIN": "Basis Coin", + "BASISSHAREV1": "Basis Share", + "BASISSHAREV2": "Basis Share", "BASK": "BasketDAO", "BAST": "Bast", "BASTET": "Bastet Goddess", @@ -1679,6 +1699,7 @@ "BCA": "Bitcoin Atom", "BCAC": "Business Credit Alliance Chain", "BCAI": "Bright Crypto Ai", + "BCAK": "BCAK", "BCAP": "Blockchain Capital", "BCAPV1": "Blockchain Capital v1", "BCAT": "BitClave", @@ -1735,6 +1756,7 @@ "BCZERO": "Buggyra Coin Zero", "BD": "BlastDEX", "BD20": "BRC-20 DEX", + "BDAG": "BlockDAG", "BDAY": "Birthday Cake", "BDB": "Big Data Block", "BDC": "BILLION•DOLLAR•CAT", @@ -1978,6 +2000,7 @@ "BIGSB": "BigShortBets", "BIGTIME": "Big Time", "BIGTOWN": "Burp", + "BIGTROUT": "The Big Trout", "BIGUP": "BigUp", "BIH": "BitHostCoin", "BIHU": "Key", @@ -1993,6 +2016,8 @@ "BIN": "Binemon", "BINA": "Binance Mascort Dog", "BINAN": "Binance Mascot", + "BINANCEAI": "Binance AI", + "BINANCEAIPRO": "Binance Ai Pro", "BINANCED": "BinanceDog On Sol", "BINANCEDOG": "Binancedog", "BINANCIENS": "Binanciens", @@ -2101,6 +2126,7 @@ "BITUSD": "bitUSD", "BITV": "Bitvolt", "BITVOLT": "BitVolt", + "BITWHITE": "BitWhite", "BITWORLD": "Bit World Token", "BITX": "BitScreener", "BITXOXO": "Bitxoxo", @@ -2342,6 +2368,7 @@ "BNPL": "BNPL Pay", "BNR": "BiNeuro", "BNRTX": "BnrtxCoin", + "BNRY": "Binary Coin", "BNS": "BNS token", "BNSAI": "bonsAI Network", "BNSD": "BNSD Finance", @@ -2486,7 +2513,8 @@ "BOOKIE": "BookieBot", "BOOKO": "Book of Pets", "BOOKOF": "BOOK OF NOTHING", - "BOOM": "Boomco", + "BOOM": "Boom", + "BOOMCO": "BOOM", "BOOMCOIN": "Boom Token", "BOOMDAO": "BOOM DAO", "BOOMER": "Boomer", @@ -2524,11 +2552,13 @@ "BOSSBABY": "BossBaby", "BOSSBURGER": "Boss Burger", "BOSSCOQ": "THE COQFATHER", + "BOSSIE": "BOSSIE", "BOST": "BoostCoin", "BOSU": "Bosu Inu", - "BOT": "Bot Planet", + "BOT": "HyperBot", "BOTC": "BotChain", "BOTIFY": "BOTIFY", + "BOTPLANET": "Bot Planet", "BOTS": "ArkDAO", "BOTTO": "Botto", "BOTX": "BOTXCOIN", @@ -2711,7 +2741,7 @@ "BSAFU": "BlockSAFU", "BSAI": "Bitcoin Silver AI", "BSATOSHI": "BabySatoshi", - "BSB": "Based Street Bets", + "BSB": "Block Street", "BSC": "BSC Layer", "BSCAKE": "Bunscake", "BSCBURN": "BSCBURN", @@ -2881,9 +2911,10 @@ "BTTY": "Bitcointry Token", "BTU": "BTU Protocol", "BTV": "Bitvote", - "BTW": "BitWhite", + "BTW": "Bitway", "BTX": "Bitradex Token", "BTXC": "Bettex coin", + "BTXEX": "BTXEX", "BTY": "Bityuan", "BTYC": "BigTycoon", "BTZ": "BitzCoin", @@ -3103,6 +3134,7 @@ "CAL": "FitBurn", "CALC": "CaliphCoin", "CALCI": "Calcium", + "CALCIFY": "CalcifyTech", "CALI": "CaliCoin", "CALL": "Global Crypto Alliance", "CALLISTO": "Callisto Network", @@ -3144,6 +3176,7 @@ "CAPY": "Capybara", "CAPYBARA": "Capybara", "CAPYBARA1995": "Capybara", + "CAPYBARACOIN": "Capybara", "CAR": "Central African Republic Meme", "CARAT": "AlaskaGoldRush", "CARATSTOKEN": "Carats Token", @@ -3201,6 +3234,7 @@ "CATCO": "CatCoin", "CATCOIN": "CatCoin", "CATCOINETH": "Catcoin", + "CATCOINIO": "Catcoin", "CATCOINOFSOL": "Cat Coin", "CATCOINV2": "CatCoin Cash", "CATDOG": "Cat-Dog", @@ -3499,6 +3533,7 @@ "CHH": "Chihuahua Token", "CHI": "Chi Gastoken", "CHIB": "Chiba Inu", + "CHIBI": "Chibification", "CHICA": "CHICA", "CHICKS": "SolChicks", "CHIDO": "Chinese Doge Wow", @@ -3583,6 +3618,7 @@ "CIC": "Crazy Internet Coin", "CICHAIN": "CIChain", "CIF": "Crypto Improvement Fund", + "CIFRON": "Cipher Mining (Ondo Tokenized)", "CIG": "cig", "CIM": "COINCOME", "CIN": "CinderCoin", @@ -3718,6 +3754,7 @@ "CMPT": "Spatial Computing", "CMPV2": "Caduceus Protocol", "CMQ": "Communique", + "CMR": "U.S Critical Mineral Reserve", "CMS": "COMSA", "CMSN": "The Commission", "CMT": "CyberMiles", @@ -3761,6 +3798,7 @@ "COBE": "Castle of Blackwater", "COBY": "Coby", "COC": "Coin of the champions", + "COCA": "COCA", "COCAINE": "THE GOOD STUFF", "COCK": "Shibacock", "COCO": "coco", @@ -4017,6 +4055,7 @@ "CRBRUS": "Cerberus", "CRC": "CryCash", "CRCL": "Circle", + "CRCLON": "Circle Internet Group (Ondo Tokenized)", "CRCLX": "Circle xStock", "CRD": "CRD Network", "CRDC": "Cardiocoin", @@ -4101,6 +4140,7 @@ "CRTAI": "CRT AI Network", "CRTB": "Coritiba F.C. Fan Token", "CRTM": "Cryptum", + "CRTR": "CREATOR", "CRTS": "Cratos", "CRU": "Crust Network", "CRUD": "CRUDE OIL BRENT", @@ -4125,6 +4165,7 @@ "CRYPT": "CryptCoin", "CRYPTAL": "CrypTalk", "CRYPTER": "Crypteriumcoin", + "CRYPTO": "Cryptocurrency Coin", "CRYPTOA": "CryptoAI", "CRYPTOAGENT": "CRYPTO AGENT TRUMP", "CRYPTOAI": "CryptoAI", @@ -4335,11 +4376,12 @@ "CYBONK": "CYBONK", "CYBR": "CYBR", "CYBRO": "Cybro Token", - "CYC": "Cyclone Protocol", + "CYC": "Cycle Network Token", "CYCAT": "Chi Yamada Cat", "CYCE": "Crypto Carbon Energy", "CYCEV1": "Crypto Carbon Energy v1", "CYCLE": "Cycle Finance", + "CYCLONEPROTOCOL": "Cyclone Protocol", "CYCLUB": "Cyclub", "CYCON": "CONUN", "CYDER": "Cyder Coin", @@ -4359,6 +4401,7 @@ "CYS": "Cysic", "CYT": "Cryptokenz", "CZ": "CHANGPENG ZHAO (changpengzhao.club)", + "CZAI": "CZ AI Agent", "CZBOOK": "CZ BOOK", "CZBROCCOLI": "Cz Broccoli", "CZC": "Crazy Coin", @@ -4391,7 +4434,8 @@ "DACS": "Dacsee", "DACXI": "Dacxi", "DAD": "DAD", - "DADA": "DADA", + "DADA": "DADACOIN", + "DADACOINTOP": "DADA", "DADDY": "Daddy Tate", "DADDYCHILL": "Daddy Chill", "DADDYDOGE": "Daddy Doge", @@ -4630,6 +4674,7 @@ "DEFIL": "DeFIL", "DEFILAB": "Defi", "DEFISCALE": "DeFiScale", + "DEFISSI": "DEFI.ssi", "DEFIT": "Digital Fitness", "DEFLA": "Defla", "DEFLCT": "Deflect", @@ -4846,7 +4891,8 @@ "DILIGENT": "Diligent Pepe", "DILL": "dillwifit", "DIM": "DIMCOIN", - "DIME": "DimeCoin", + "DIME": "DIME", + "DIMECOIN": "DimeCoin", "DIMO": "DIMO", "DIN": "DIN", "DINE": "Dinero", @@ -4875,6 +4921,7 @@ "DISCOVERY": "DiscoveryIoT", "DISK": "Dark Lisk", "DISPEPE": "Disabled Pepe", + "DISTORTED": "Distorted Face", "DISTR": "Distributed Autonomous Organization", "DISTRIBUTE": "DISTRIBUTE", "DIT": "Ditcoin", @@ -5314,7 +5361,8 @@ "DUCKIES": "Yellow Duckies", "DUCKO": "Duck Off Coin", "DUCKV1": "UNITPROV1", - "DUCKY": "Ducky Duck", + "DUCKY": "Ducky", + "DUCKY0X71": "Ducky Duck", "DUCX": "DucatusX", "DUDE": "DuDe", "DUEL": "GameGPT", @@ -5482,6 +5530,7 @@ "ECHOD": "EchoDEX", "ECHT": "e-Chat", "ECI": "Euro Cup Inu", + "ECKODAO": "eckoDAO", "ECL": "ECLAT", "ECLD": "Ethernity Cloud", "ECLIP": "Eclipse Fi", @@ -5880,7 +5929,8 @@ "ESN": "Ethersocial", "ESNC": "Galaxy Arena Metaverse", "ESOL": "Earn Solana", - "ESP": "Espers", + "ESP": "Espresso", + "ESPERS": "Espers", "ESPL": "ESPL ARENA", "ESPORTS": "Yooldo Games", "ESPR": "Espresso Bot", @@ -6239,7 +6289,7 @@ "FCT": "FirmaChain", "FCTC": "FaucetCoin", "FCTR": "FactorDAO", - "FDC": "Fidance", + "FDC": "FDrive Coin", "FDGC": "FINTECH DIGITAL GOLD COIN", "FDLS": "FIDELIS", "FDM": "Fandom", @@ -6312,6 +6362,7 @@ "FIC": "Filecash", "FID": "Fidira", "FIDA": "Bonfida", + "FIDANCE": "Fidance", "FIDD": "Fidelity Digital Dollar", "FIDLE": "Fidlecoin", "FIDO": "FIDO", @@ -6323,7 +6374,8 @@ "FIFTY": "FIFTYONEFIFTY", "FIG": "FlowCom", "FIGH": "FIGHT FIGHT FIGHT", - "FIGHT": "Fight to MAGA", + "FIGHT": "FIGHT", + "FIGHT2MAGA": "Fight to MAGA", "FIGHTMAGA": "FIGHT MAGA", "FIGHTPEPE": "FIGHT PEPE", "FIGHTRUMP": "FIGHT TRUMP", @@ -6594,6 +6646,7 @@ "FORTKNOX": "Fort Knox", "FORTUNA": "Fortuna", "FORTUNE": "Fortune", + "FORU": "ForU AI", "FORWARD": "Forward Protocol", "FOTA": "Fight Of The Ages", "FOTO": "Unique Photo", @@ -6628,6 +6681,7 @@ "FR": "Freedom Reserve", "FRA": "Findora", "FRAC": "FractalCoin", + "FRACTON": "Fracton Protocol", "FRAG": "Fragmetric", "FRANK": "Frank", "FRANKLIN": "Franklin", @@ -6651,10 +6705,13 @@ "FREEDO": "Freedom", "FREEDOG": "Freedogs", "FREEDOM": "Freedom Protocol Token", + "FREEDOMOFMONEY": "Freedom of Money", "FREELA": "DecentralFree", "FREEPAVEL": "Free Pavel", "FREEROSS": "FreeRossDAO", "FREET": "FreeTrump", + "FREEWAYV1": "Freeway Token", + "FREEWAYV2": "Freeway Token", "FREL": "Freela", "FREN": "FREN", "FRENC": "Frencoin", @@ -6726,7 +6783,7 @@ "FSTC": "FastCoin", "FSTR": "Fourth Star", "FSW": "Falconswap", - "FT": "Fracton Protocol", + "FT": "Flying Tulip", "FTB": "Fit&Beat", "FTC": "Futurex", "FTD": "42DAO", @@ -6820,8 +6877,9 @@ "FWCL": "Legends", "FWH": "FigureWifHat", "FWOG": "Fwog", - "FWT": "Freeway Token", + "FWT": "FadeWallet Token", "FWW": "Farmers World Wood", + "FWX": "Future Warriors X", "FX": "Function X", "FXAKV": "Akiverse Governance", "FXB": "FxBox", @@ -6854,6 +6912,7 @@ "GAD": "Green App Development", "GAFA": "Gafa", "GAFI": "GameFi", + "GAG": "GAG Token", "GAGA": "Gaga", "GAI": "GraphAI", "GAIA": "Gaia Token", @@ -6898,9 +6957,10 @@ "GAMEIN": "Game Infinity", "GAMER": "GameStation", "GAMERFI": "GamerFI", - "GAMES": "Gamestarter", + "GAMES": "GAME•OF•BITCOIN", "GAMEST": "GameStop Coin", "GAMESTARS": "Game Stars", + "GAMESTARTER": "Gamestarter", "GAMESTO": "GameStop", "GAMESTOP": "GameStop", "GAMESTUMP": "GAMESTUMP", @@ -7055,6 +7115,7 @@ "GEO": "GeoCoin", "GEOD": "GEODNET", "GEODB": "GeoDB", + "GEODE": "Geode Chain", "GEOJ": "Geojam", "GEOL": "GeoLeaf", "GEON": "Geon", @@ -7070,6 +7131,7 @@ "GETA": "Getaverse", "GETH": "Guarded Ether", "GETLIT": "LIT", + "GETRICHQUICK": "GET RICH QUICK", "GETX": "Guaranteed Ethurance Token Extra", "GEX": "Gexan", "GEZY": "EZZY GAME GEZY", @@ -7409,7 +7471,7 @@ "GOW39": "God Of Wealth", "GOYOO": "GoYoo", "GOZ": "Göztepe S.K. Fan Token", - "GP": "Wizards And Dragons", + "GP": "Graphite", "GPAWS": "Golden Paws", "GPBP": "Genius Playboy Billionaire Philanthropist", "GPCX": "Good Person Coin", @@ -7417,11 +7479,13 @@ "GPKR": "Gold Poker", "GPL": "Gold Pressed Latinum", "GPLX": "Gplx", + "GPM": "GOLD PUMP MEME", "GPN": "Gamepass Network", "GPO": "GoldPesa Option", "GPPT": "Pluto Project Coin", "GPRO": "GoldPro", "GPS": "GoPlus Security", + "GPST": "GPStarter", "GPSTOKEN": "GPS Token", "GPT": "QnA3.AI", "GPT4O": "GPT-4o", @@ -7445,7 +7509,8 @@ "GRAM": "Gram", "GRAND": "Grand Theft Ape", "GRANDCOIN": "GrandCoin", - "GRANDMA": "Grandma", + "GRANDMA": "Minecraft Grandma Fund", + "GRANDMASOL": "Grandma", "GRANT": "GrantiX Token", "GRAPE": "GrapeCoin", "GRAPHGRAIAI": "GraphGrail AI", @@ -7671,10 +7736,12 @@ "HAC": "Hackspace Capital", "HACD": "Hacash Diamond", "HACH": "Hachiko", - "HACHI": "Hachi", + "HACHI": "Hachiko", "HACHIK": "Hachiko", "HACHIKO": "Hachiko Inu Token", + "HACHIKOINU": "Hachiko Inu", "HACHIONB": "Hachi On Base", + "HACHITOKEN": "Hachi", "HACK": "HACK", "HADES": "Hades", "HAEDAL": "Haedal Protocol", @@ -7721,6 +7788,7 @@ "HAR": "Harambe Coin", "HARAM": "HARAM", "HARAMBE": "Harambe on Solana", + "HARAMBEAI": "Harambe AI Token", "HARD": "Kava Lend", "HARE": "Hare Token", "HAREPLUS": "Hare Plus", @@ -7867,6 +7935,7 @@ "HFUN": "Hypurr Fun", "HGEN": "HGEN DAO", "HGET": "Hedget", + "HGG": "Hedera Guild Game", "HGHG": "HUGHUG Coin", "HGO": "HireGo", "HGOLD": "HollyGold", @@ -7982,6 +8051,7 @@ "HNC": "Hellenic Coin", "HNCN": "Huncoin", "HND": "Hundred Finance", + "HNO": "HNO Coin", "HNS": "Handshake", "HNST": "Honest", "HNT": "Helium", @@ -8039,6 +8109,7 @@ "HONOR": "HonorLand", "HONX": "Honeywell xStock", "HOODOG": "Hoodog", + "HOODON": "Robinhood Markets (Ondo Tokenized)", "HOODRAT": "Hoodrat Coin", "HOODX": "Robinhood xStock", "HOOF": "Metaderby Hoof", @@ -8311,6 +8382,7 @@ "IDOLINU": "IDOLINU", "IDOODLES": "IDOODLES", "IDORU": "Vip2Fan", + "IDOS": "IDOS", "IDRISS": "IDRISS", "IDRT": "Rupiah Token", "IDRX": "IDRX", @@ -8395,6 +8467,7 @@ "IMS": "Independent Money System", "IMST": "Imsmart", "IMT": "Immortal Token", + "IMU": "Immunefi", "IMUSIFY": "imusify", "IMVR": "ImmVRse", "IMX": "Immutable X", @@ -8452,6 +8525,7 @@ "INN": "Innova", "INNBC": "Innovative Bioresearch Coin", "INNOU": "Innou", + "INNOVAMINEX": "InnovaMinex", "INO": "Ino Coin", "INOVAI": "INOVAI", "INP": "Ionic Pocket Token", @@ -8464,6 +8538,7 @@ "INSANITY": "Insanity Coin", "INSC": "INSC (Ordinals)", "INSE": "INSECT", + "INSIGHTPROTOCOL": "Insight Protocol", "INSN": "Industry Sonic", "INSP": "Inspect", "INSPI": "InspireAI", @@ -8502,7 +8577,7 @@ "INVITE": "INVITE Token", "INVOX": "Invox Finance", "INVX": "Investx", - "INX": "Insight Protocol", + "INX": "Infinex", "INXM": "InMax", "INXT": "Internxt", "INXTOKEN": "INX Token", @@ -8630,6 +8705,7 @@ "IVIP": "iVipCoin", "IVN": "IVN Security", "IVPAY": "ivendPay", + "IVT": "ivault Token", "IVY": "IvyKoin", "IVZ": "InvisibleCoin", "IW": "iWallet", @@ -8750,6 +8826,7 @@ "JFIVE": "Jonny Five", "JFOX": "JuniperFox AI", "JFP": "JUSTICE FOR PEANUT", + "JGGL": "JGGL Token", "JGLP": "Jones GLP", "JGN": "Juggernaut", "JHH": "Jen-Hsun Huang", @@ -8872,6 +8949,7 @@ "JUP": "Jupiter", "JUPI": "Jupiter", "JUPSOL": "Jupiter Staked SOL", + "JUPUSD": "Jupiter USD", "JUR": "Jur", "JUS": "Just The Tip", "JUSD": "JUSD Stable Token", @@ -8956,6 +9034,7 @@ "KARA": "KarateCat", "KARAT": "KARAT Galaxy", "KARATE": "Karate Combat", + "KARATTOKEN": "Karat", "KAREN": "KarenCoin", "KARMA": "Karma", "KARMAD": "Karma DAO", @@ -8971,10 +9050,9 @@ "KASSIAHOME": "Kassia Home", "KASTA": "Kasta", "KASTER": "King Aster", - "KAT": "Karat", + "KAT": "Katana Network", "KATA": "Katana Inu", "KATANA": "Katana Finance", - "KATANANET": "Katana Network", "KATCHU": "Katchu Coin", "KATT": "Katt Daddy", "KATYCAT": "Katy Perry Fans", @@ -8984,6 +9062,7 @@ "KAWA": "Kawakami Inu", "KAWS": "Kaws", "KAYI": "Kayı", + "KAYYO": "Kayyo", "KBBB": "KILL BIG BEAUTIFUL BILL", "KBC": "Karatgold coin", "KBD": "Kyberdyne", @@ -9015,7 +9094,7 @@ "KDOE": "Kudoe", "KDOGE": "KingDoge", "KDT": "Kenyan Digital Token", - "KDX": "eckoDAO", + "KDX": "Kodexa", "KEANU": "Keanu Inu", "KEC": "KEYCO", "KED": "Klingon Empire Darsek", @@ -9100,11 +9179,15 @@ "KIMA": "Kima", "KIMBA": "The White Lion", "KIMBO": "Kimbo", - "KIMCHI": "KIMCHI.finance", + "KIMCH": "Kimchi", + "KIMCHI": "Kimchi Coin", + "KIMCHICTO": "Kimchi", + "KIMCHIFINANCE": "KIMCHI.finance", "KIMIAI": "Kimi AI Agent", - "KIN": "Kin", + "KIN": "KinToken", "KIND": "Kind Ads", "KINE": "Kine Protocol", + "KINECOSYSTEM": "Kin", "KINET": "KinetixFi", "KING": "LRT Squared", "KING93": "King93", @@ -9223,6 +9306,7 @@ "KNUT": "Knut From Zoo", "KNUXX": "Knuxx Bully of ETH", "KNW": "Knowledge", + "KNX": "KnoxNet", "KO": "Kyuzo's Friends", "KOAI": "KOI", "KOALA": "KOALA", @@ -9366,6 +9450,7 @@ "KUSH": "KushCoin", "KUSUNOKI": "Kusunoki Samurai", "KUV": "Kuverit", + "KVAI": "Kvants AI", "KVERSE": "KEEPs Coin", "KVI": "KVI Chain", "KVNT": "KVANT", @@ -9384,7 +9469,7 @@ "KXUSD": "kxUSD", "KYCC": "KYCCOIN", "KYL": "Kylin Network", - "KYO": "Kayyo", + "KYO": "Kyo", "KYOKO": "Kyoko", "KYRA": "KYRA", "KYSOL": "Kyros Restaked SOL", @@ -9557,6 +9642,7 @@ "LENARD": "Lenard", "LEND": "Aave", "LENDA": "Lenda", + "LENDFLARE": "Lend Flare Dao", "LENDS": "Lends", "LENFI": "Lenfi", "LENIN": "LeninCoin", @@ -9601,7 +9687,7 @@ "LFI": "LunaFi", "LFIT": "LFIT", "LFNTY": "Lifinity", - "LFT": "Lend Flare Dao", + "LFT": "LIFEFORM Token", "LFW": "Linked Finance World", "LGBT": "Let's Go Brandon Token", "LGBTQ": "LGBTQoin", @@ -9707,6 +9793,7 @@ "LISTEN": "Listen", "LISUSD": "lisUSD", "LIT": "Lighter", + "LITCOIN": "Litcoin", "LITE": "Lite USD", "LITEBTC": "LiteBitcoin", "LITENETT": "Litenett", @@ -9776,6 +9863,7 @@ "LNQ": "LinqAI", "LNR": "LNR", "LNRV2": "Lunar", + "LNS": "LIFE Coin", "LNT": "Lottonation", "LNX": "Lunox Token", "LOA": "League of Ancients", @@ -9784,6 +9872,7 @@ "LOAN": "Lendoit", "LOBO": "LOBO•THE•WOLF•PUP", "LOBS": "Lobstex", + "LOBSTAR": "Lobstar", "LOC": "LockTrip", "LOCAT": "LOVE CAT", "LOCC": "Low Orbit Crypto Cannon", @@ -9816,6 +9905,7 @@ "LOLLY": "Lollipop", "LOLLYBOMB": "LollyBomb", "LOLO": "Lolo", + "LOLONBSC": "LOL", "LON": "Tokenlon", "LONG": "Longdrink Finance", "LONGEVITY": "longevity", @@ -9829,8 +9919,9 @@ "LOOMV1": "Loom Network v1", "LOON": "Loon Network", "LOONG": "PlumpyDragons", - "LOOP": "LOOP", + "LOOP": "LoopNetwork", "LOOPIN": "LooPIN Network", + "LOOPMARKETS": "LOOP", "LOOPY": "Loopy", "LOOT": "LootBot", "LOOTEX": "Lootex", @@ -9891,7 +9982,7 @@ "LRN": "Loopring [NEO]", "LRT": "LandRocker", "LSC": "LS Coin", - "LSD": "Pontem Liquidswap", + "LSD": "LSD", "LSDOGE": "LSDoge", "LSETH": "Liquid Staked ETH", "LSHARE": "LSHARE", @@ -9974,8 +10065,9 @@ "LULU": "LULU", "LUM": "Luminous", "LUMA": "LUMA Token", - "LUMI": "LUMI Credits", + "LUMI": "LumiShare", "LUMIA": "Lumia", + "LUMICREDITS": "LUMI Credits", "LUMIO": "Solana Mascot", "LUMO": "Lumo-8B-Instruct", "LUMOS": "Lumos", @@ -10065,6 +10157,7 @@ "MABA": "Make America Based Again", "MAC": "MachineCoin", "MACHO": "macho", + "MACMINI": "Mac mini", "MACRO": "Macro Millions", "MACROPROTOCOL": "Macro Protocol", "MADA": "MilkADA", @@ -10164,11 +10257,11 @@ "MANTA": "Manta Network", "MANTI": "Mantis", "MANTLE": "Mantle", + "MANTRA": "MANTRA", "MANUSAI": "Manus AI Agent", "MANYU": "Manyu", "MANYUDOG": "MANYU", - "MAO": "MAO", - "MAOMEME": "Mao", + "MAO": "Mao", "MAOW": "MAOW", "MAP": "MAP Protocol", "MAPC": "MapCoin", @@ -10197,10 +10290,11 @@ "MARMAJ": "marmaj", "MAROV1": "TTC PROTOCOL", "MAROV2": "Maro", - "MARS": "Mars", + "MARS": "MetaMars", "MARS4": "MARS4", "MARSC": "MarsCoin", "MARSCOIN": "MarsCoin", + "MARSERC": "Mars", "MARSH": "Unmarshal", "MARSMI": "MarsMi", "MARSO": "Marso.Tech", @@ -10531,6 +10625,7 @@ "METANIA": "METANIA V2", "METANIAV1": "METANIAGAMES", "METANO": "Metano", + "METAON": "Meta Platforms (Ondo Tokenized)", "METAPK": "Metapocket", "METAPLACE": "Metaplace", "METAQ": "MetaQ", @@ -10631,6 +10726,7 @@ "MICRO": "Micro GPT", "MICRODOGE": "MicroDoge", "MICROMINES": "Micromines", + "MICROVISION": "MicroVisionChain", "MIDAI": "Midway AI", "MIDAS": "Midas", "MIDASDOLLAR": "Midas Dollar Share", @@ -10725,7 +10821,7 @@ "MINTYS": "MintySwap", "MINU": "Minu", "MINUTE": "MINUTE Vault (NFTX)", - "MINX": "InnovaMinex", + "MINX": "Modern Innovation Network Token", "MIO": "Miner One token", "MIODIO": "MIODIOCOIN", "MIOTA": "IOTA", @@ -10935,7 +11031,9 @@ "MOLK": "Mobilink Token", "MOLLARS": "MollarsToken", "MOLLY": "Molly", + "MOLO": "MOLO CHAIN", "MOLT": "Moltbook", + "MOLTID": "MoltID", "MOM": "Mother of Memes", "MOMA": "Mochi Market", "MOMIJI": "MAGA Momiji", @@ -10972,6 +11070,7 @@ "MONKAS": "Monkas", "MONKE": "Monkecoin", "MONKEY": "Monkey", + "MONKEYC": "Monkey Cult", "MONKEYS": "Monkeys Token", "MONKU": "Monku", "MONKY": "Wise Monkey", @@ -10984,6 +11083,7 @@ "MONST": "Monstock", "MONSTA": "Cake Monster", "MONSTE": "Monster", + "MONSTRO": "Monstro DeFi", "MONT": "Monarch Token", "MONTE": "Monte", "MOO": "MooMonster", @@ -11013,6 +11113,7 @@ "MOOND": "Dark Moon", "MOONDAY": "Moonday Finance", "MOONDO": "MOON DOGE", + "MOONDOG": "MOONDOGE", "MOONDOGE": "MOONDOGE", "MOONED": "MoonEdge", "MOONER": "CoinMooner", @@ -11063,6 +11164,7 @@ "MOUNTA": "Mountain Protocol", "MOUTAI": "Moutai", "MOV": "MovieCoin", + "MOVA": "MOVA", "MOVD": "MOVE Network", "MOVE": "Movement", "MOVER": "Mover", @@ -11093,6 +11195,7 @@ "MPLUS": "M+Plus", "MPLX": "Metaplex", "MPM": "Monopoly Meta", + "MPP": "MegPrime Token", "MPRO": "MediumProject", "MPS": "Mt Pelerin Shares", "MPT": "Miracleplay Token", @@ -11163,6 +11266,7 @@ "MSTAR": "MerlinStarter", "MSTETH": "Eigenpie mstETH", "MSTO": "Millennium Sapphire", + "MSTRON": "MicroStrategy (Ondo Tokenized)", "MSTRX": "MicroStrategy xStock", "MSU": "MetaSoccer", "MSUSHI": "Sushi (Multichain)", @@ -11250,6 +11354,7 @@ "MUON": "Micron Technology (Ondo Tokenized)", "MURA": "Murasaki", "MURATIAI": "MuratiAI", + "MUSA": "Mansa AI", "MUSCAT": "MusCat", "MUSD": "MetaMask USD", "MUSDC": "USD Coin (Multichain)", @@ -11648,6 +11753,7 @@ "NIC": "NewInvestCoin", "NICE": "Nice", "NICEC": "NiceCoin", + "NICKELS": "Nickel", "NIETZSCHEAN": "Nietzschean Penguin", "NIF": "Unifty", "NIFT": "Niftify", @@ -11748,6 +11854,7 @@ "NOHAT": "DogWifNoHat", "NOIA": "Syntropy", "NOICE": "noice", + "NOIRSHARES": "NoirShares", "NOIS": "Nois Network", "NOIZ": "NOIZ", "NOKA": "Noka Solana AI", @@ -11821,7 +11928,7 @@ "NRN": "Neuron", "NRO": "Neuro", "NRP": "Neural Protocol", - "NRS": "NoirShares", + "NRS": "Nereus", "NRV": "Nerve Finance", "NRVE": "Narrative", "NRX": "Neironix", @@ -12019,6 +12126,8 @@ "ODDZ": "Oddz", "ODE": "ODEM", "ODGN": "OrdiGen", + "ODIC": "ODIC Token", + "ODIK": "ODIK", "ODIN": "Odin Protocol", "ODMC": "ODMCoin", "ODN": "Obsidian", @@ -12207,12 +12316,15 @@ "OPENCHAT": "OpenChat", "OPENCUSTODY": "Open Custody Protocol", "OPENDAO": "OpenDAO", + "OPENECO": "OPEN Ticketing Ecosystem", + "OPENECOV1": "GET Protocol", "OPENGO": "OPEN Governance Token", "OPENON": "Opendoor Technologies (Ondo Tokenized)", "OPENP": "Open Platform", "OPENRI": "Open Rights Exchange", "OPENSOURCE": "Open Source Network", "OPENSWAP": "OpenSwap Optimism Token", + "OPENV": "OpenVPP", "OPENVC": "OpenVoiceCoin", "OPENW": "OpenWorld", "OPENX": "OpenxAI", @@ -12226,9 +12338,8 @@ "OPINU": "Optimus Inu", "OPIUM": "Opium", "OPMND": "Open Mind Network", - "OPN": "OPEN Ticketing Ecosystem", + "OPN": "OPINION", "OPNN": "Opennity", - "OPNV1": "GET Protocol", "OPP": "Opporty", "OPS": "Octopus Protocol", "OPSC": "OpenSourceCoin", @@ -12468,6 +12579,7 @@ "PANGEA": "PANGEA", "PANIC": "PanicSwap", "PANO": "PanoVerse", + "PANTH": "Panther AI", "PANTHER": "Panther Protocol", "PANTOS": "Pantos", "PAO": "South Pao", @@ -12614,7 +12726,7 @@ "PEAN": "Peanut the Squirrel (peanut-token.xyz)", "PEANIE": "Peanie", "PEANU": "PEANUT INU", - "PEANUT": "#1 Tiktok Squirrel", + "PEANUT": "Peanut", "PEAQ": "peaq", "PEAR": "Pear Swap", "PEARL": "Pearl Finance", @@ -12668,9 +12780,11 @@ "PENGYX": "PengyX", "PENIS": "PenisGrow", "PENJ": "Penjamin Blinkerton", + "PENNIES": "Penny", "PENP": "Penpie", "PENR": "Penrose Finance", "PENTA": "Penta", + "PENTAG": "Pentagon", "PEON": "Peon", "PEOPLE": "ConstitutionDAO", "PEOPLEFB": "PEOPLE", @@ -12726,6 +12840,7 @@ "PEPEPI": "PEPEPi", "PEPER": "Baby Pepe", "PEPERA": "PEPERA", + "PEPESDOG": "Pepes Dog", "PEPESOL": "PEPE SOL", "PEPESOLCTO": "Pepe (pepesolcto.vip)", "PEPESORA": "Pepe Sora AI", @@ -12770,6 +12885,7 @@ "PESOBIT": "PesoBit", "PESTO": "Pesto the Baby King Penguin", "PET": "Hello Pets", + "PETAH": "ピータさん", "PETE": "PETE", "PETERTODD": "Peter Todd", "PETF": "PEPE ETF", @@ -12885,6 +13001,7 @@ "PIKE": "Pike Token", "PIKO": "Pinnako", "PIKZ": "PIKZ", + "PILL": "life changing pill", "PILLAR": "PillarFi", "PILOT": "Unipilot", "PIM": "PIM", @@ -12915,6 +13032,7 @@ "PIPO": "Pipo", "PIPONHL": "PiP", "PIPPIN": "pippin", + "PIPPKIN": "Pippkin The Horse", "PIPT": "Power Index Pool Token", "PIRATE": "Pirate Nation", "PIRATECASH": "PirateCash", @@ -13056,6 +13174,7 @@ "PMR": "Pomerium Utility Token", "PMT": "Public Masterpiece Token", "PMTN": "Peer Mountain", + "PMUSD": "Precious Metals USD", "PMX": "Phillip Morris xStock", "PNB": "Pink BNB", "PNC": "PlatiniumCoin", @@ -13146,6 +13265,7 @@ "PONKE": "Ponke", "PONKEBNB": "Ponke BNB", "PONKEI": "Chinese Ponkei the Original", + "PONTEM": "Pontem Liquidswap", "PONYO": "Ponyo Impact", "PONZI": "Ponzi", "PONZIO": "Ponzio The Cat", @@ -13425,8 +13545,9 @@ "PUMPTRUMP": "PUMP TRUMP", "PUMPY": "WOW MOON LAMBO PUMPPPPPPY", "PUN": "Punkko", - "PUNCH": "PUNCHWORD", + "PUNCH": "パンチ (Punch) (punchonsolana.fun)", "PUNCHI": "Punchimals", + "PUNCHWORD": "PUNCHWORD (punchword.com)", "PUNDIAI": "Pundi AI", "PUNDIX": "Pundi X", "PUNDU": "Pundu", @@ -13435,7 +13556,7 @@ "PUNK": "PunkCity", "PUNKAI": "PunkAI", "PUNKV": "Punk Vault (NFTX)", - "PUP": "Puppy Coin", + "PUP": "PUP", "PUPA": "PupaCoin", "PUPPER": "Pupper", "PUPPET": "Puppet", @@ -13573,6 +13694,7 @@ "QNX": "QueenDex Coin", "QOBI": "Qobit", "QOM": "Shiba Predator", + "QONE": "QONE", "QOOB": "QOOBER", "QORA": "QoraCoin", "QORPO": "QORPO WORLD", @@ -13580,6 +13702,7 @@ "QQBC": "QQBC IPFS BLOCKCHAIN", "QQQ": "Poseidon Network", "QQQF": "Standard Crypto Fund", + "QQQON": "Invesco QQQ (Ondo Tokenized)", "QQQX": "Nasdaq xStock", "QR": "Qrolli", "QRK": "QuarkCoin", @@ -13654,6 +13777,7 @@ "QWT": "QoWatt", "QXC": "QuantumXC", "R1": "Recast1", + "R2": "R2", "R2R": "CitiOs", "R34P": "R34P", "R3FI": "r3fi.finance", @@ -13692,6 +13816,7 @@ "RAILS": "Rails Token", "RAIN": "Rain", "RAINBOW": "Rainbow Token", + "RAINBOWTOKEN": "Rainbow Token", "RAINC": "RainCheck", "RAINCO": "Rain Coin", "RAINI": "Rainicorn", @@ -13764,7 +13889,7 @@ "RBXDEFI": "RBX", "RBXS": "RBXSamurai", "RBY": "RubyCoin", - "RC": "Russiacoin", + "RC": "Rebel Cars", "RC20": "RoboCalls", "RCADE": "RCADE", "RCC": "Reality Clash", @@ -13864,6 +13989,7 @@ "REFUND": "Refund", "REG": "RealToken Ecosystem Governance", "REGALCOIN": "Regalcoin", + "REGARISK": "REGA Risk Sharing Token", "REGE": "Regent of the North Winds", "REGEN": "Regen Network", "REGENT": "REGENT COIN", @@ -13927,7 +14053,8 @@ "RETH": "Rocket Pool ETH", "RETH2": "rETH2", "RETIK": "Retik Finance", - "RETIRE": "Retire Token", + "RETIRE": "The Last Play", + "RETIRETOKEN": "Retire Token", "RETSA": "Retsa Coin", "REU": "REUCOIN", "REUNI": "Reunit Wallet", @@ -13980,6 +14107,7 @@ "RHOC": "RChain", "RHP": "Rhypton Club", "RHUB": "ROLLHUB", + "RHYPURR": "rHYPURR", "RIA": "aRIA Currency", "RIB": "Ribus", "RIBB": "Ribbit", @@ -13988,7 +14116,7 @@ "RICE": "RICE AI", "RICECOIN": "RiceCoin", "RICEFARM": "RiceFarm", - "RICH": "GET RICH QUICK", + "RICH": "Ostrich", "RICHCOIN": "RICHCOIN", "RICHIE": "Richie2.0", "RICHIEV1": "Richie", @@ -14080,7 +14208,7 @@ "RMV": "Reality Metaverse", "RNAPEPE": "RNA PEPE", "RNB": "Rentible", - "RNBW": "Rainbow Token", + "RNBW": "Rainbow", "RNC": "ReturnCoin", "RND": "The RandomDAO", "RNDR": "Render Token", @@ -14094,15 +14222,16 @@ "ROA": "ROA CORE", "ROAD": "ROAD", "ROAM": "Roam Token", - "ROAR": "Alpha DEX", + "ROAR": "Roaring Kitty", "ROARINGCAT": "Roaring Kitty", "ROB": "ROB", "ROBET": "RoBet", "ROBI": "Robin Rug", "ROBIN": "Robin of Da Hood", "ROBINH": "ROBIN HOOD", - "ROBO": "RoboHero", + "ROBO": "Robo Token", "ROBOCOIN": "First Bitcoin ATM", + "ROBOHERO": "RoboHero", "ROBOTA": "TAXI", "ROBOTAXI": "ROBOTAXI", "ROC": "Rasputin Online Coin", @@ -14160,6 +14289,7 @@ "ROSX": "Roseon", "ROT": "Rotten", "ROTTY": "ROTTYCOIN", + "ROU": "ROUTINE COIN", "ROUGE": "Rouge Studio", "ROUND": "RoundCoin", "ROUP": "Roup (Ordinals)", @@ -14211,7 +14341,7 @@ "RSRV": "Reserve", "RSRV1": "Reserve Rights v1", "RSS3": "RSS3", - "RST": "REGA Risk Sharing Token", + "RST": "Runesoul", "RSTK": "Restake Finance", "RSUN": "RisingSun", "RSUSHI": "Sushi (Rainbow Bridge)", @@ -14228,6 +14358,7 @@ "RTH": "Rotharium", "RTK": "RetaFi", "RTM": "Raptoreum", + "RTP": "Return to Player", "RTR": "Restore The Republic", "RTT": "Restore Truth Token", "RTX": "RateX", @@ -14267,6 +14398,7 @@ "RUSH": "RUSH COIN", "RUSHCMC": "RUSHCMC", "RUSSELL": "Russell", + "RUSSIACOIN": "Russiacoin", "RUST": "RustCoin", "RUSTBITS": "Rustbits", "RUTH": "RUTH", @@ -14316,7 +14448,7 @@ "RYOSHI": "Ryoshis Vision", "RYS": "RefundYourSOL", "RYT": "Real Yield Token", - "RYU": "The Blue Dragon", + "RYU": "RyuJin", "RYZ": "Anryze", "RZR": "Rezor", "RZTO": "RZTO Token", @@ -14595,7 +14727,7 @@ "SECT": "SECTBOT", "SECTO": "Sector Finance", "SEDA": "SEDA Protocol", - "SEED": "Superbloom", + "SEED": "SEED", "SEEDS": "SeedShares", "SEEDV": "Seed Venture", "SEEDX": "SEEDx", @@ -14723,6 +14855,7 @@ "SHAMAN": "Shaman King Inu", "SHAN": "Shanum", "SHANG": "Shanghai Inu", + "SHAPE": "Shape", "SHAR": "Shark Cat", "SHARBI": "SHARBI", "SHARDS": "WorldShards", @@ -14915,7 +15048,8 @@ "SILV": "Silver Surfer Solana", "SILV2": "Escrowed Illuvium 2", "SILVA": "Silva Token", - "SILVER": "SILVER", + "SILVER": "silver coin", + "SILVERETHCLUB": "SILVER (silvereth.club)", "SILVERKRC": "Silver KRC-20", "SILVERNOV": "Silvernova Token", "SILVERSTAND": "Silver Standard", @@ -15139,6 +15273,7 @@ "SMURFCATSOL": "Real Smurf Cat", "SMX": "Snapmuse.io", "SN": "SpaceN", + "SN3": "Supernova Nebula3", "SNA": "SUKUYANA", "SNAC": "SnackboxAI", "SNACK": "Crypto Snack", @@ -15153,6 +15288,7 @@ "SNAP": "SnapEx", "SNAPCAT": "Snapcat", "SNAPKERO": "SNAP", + "SNAPON": "Snap (Ondo Tokenized)", "SNB": "SynchroBitcoin", "SNC": "SunContract", "SNCT": "SnakeCity", @@ -15262,6 +15398,7 @@ "SOLAREU": "Solareum", "SOLARFARM": "SolarFarm", "SOLARIX": "SOLARIX", + "SOLARX": "SolarX", "SOLAV": "SOLAV TOKEN", "SOLBANK": "Solbank", "SOLBET": "SOL STREET BETS", @@ -15270,7 +15407,8 @@ "SOLBULL": "SOLBULL", "SOLC": "SolCard", "SOLCASH": "SOLCash", - "SOLCAT": "SOLCAT", + "SOLCAT": "CatSolHat", + "SOLCATMEME": "SOLCAT", "SOLCEX": "SolCex", "SOLCHICKSSHARDS": "SolChicks Shards", "SOLE": "SoleCoin", @@ -15319,7 +15457,7 @@ "SOLVE": "SOLVE", "SOLVEX": "SOLVEX", "SOLWIF": "Solwif", - "SOLX": "SolarX", + "SOLX": "Solaxy", "SOLXD": "Solxdex", "SOLY": "Solamander", "SOLYMPICS": "Solympics", @@ -15380,7 +15518,7 @@ "SP8DE": "Sp8de", "SPA": "Sperax", "SPAC": "SPACE DOGE", - "SPACE": "MicroVisionChain", + "SPACE": "Spacecoin", "SPACECOIN": "SpaceCoin", "SPACED": "SPACE DRAGON", "SPACEHAMSTER": "Space Hamster", @@ -15719,8 +15857,8 @@ "STOGE": "Stoner Doge Finance", "STOIC": "stoicDAO", "STON": "STON", - "STONE": "Stone Token", "STONEDE": "Stone DeFi", + "STONETOKEN": "Stone Token", "STONK": "STONK", "STONKS": "STONKS", "STOP": "LETSTOP", @@ -15833,6 +15971,7 @@ "SUMMIT": "Summit", "SUMMITTHE": "SUMMIT", "SUMO": "Sumokoin", + "SUMR": "SummerToken", "SUN": "Sun Token", "SUNC": "Sunrise", "SUNCAT": "Suncat", @@ -15868,6 +16007,7 @@ "SUPERCYCLE": "Crypto SuperCycle", "SUPERDAPP": "SuperDapp", "SUPERF": "SUPER FLOKI", + "SUPERFL": "Superfluid", "SUPERGROK": "SuperGrok", "SUPEROETHB": "Super OETH", "SUPERT": "Super Trump", @@ -15886,6 +16026,7 @@ "SUSDA": "sUSDa", "SUSDE": "Ethena Staked USDe", "SUSDS": "Savings USDS", + "SUSDT": "SkyTrade Pro", "SUSDX": "Staked USDX", "SUSHI": "Sushi", "SUSX": "Savings USX", @@ -16077,6 +16218,7 @@ "TAP": "TAP FANTASY", "TAPC": "Tap Coin", "TAPCOIN": "TAP FANTASY", + "TAPP": "TAPP", "TAPPINGCOIN": "TappingCoin", "TAPROOT": "Taproot Exchange", "TAPS": "TapSwap", @@ -16207,6 +16349,7 @@ "TEMP": "Tempus", "TEMPLE": "TempleDAO", "TEN": "TEN", + "TENCENTAI": "Tencent AI", "TEND": "Tendies", "TENDIE": "TendieSwap", "TENET": "TENET", @@ -16241,6 +16384,7 @@ "TESOURO": "Etherfuse TESOURO", "TEST": "Test", "TESTA": "Testa", + "TESTICLE": "Testicle", "TET": "Tectum", "TETH": "Treehouse ETH", "TETHYS": "Tethys", @@ -16280,6 +16424,7 @@ "THEAICOIN": "AI", "THEB": "The Boys Club", "THEBLOX": "The Blox Project", + "THEBLUEDRAGON": "The Blue Dragon", "THEC": "The CocktailBar", "THECA": "Theca", "THECAT": "THECAT", @@ -16309,6 +16454,7 @@ "THETAN": "Thetan Coin", "THETRANSFERTOKEN": "The Transfer Token", "THETRIBE": "The Tribe", + "THEWHALE": "The Whale killer", "THEX": "Thore Exchange", "THG": "Thetan Arena", "THIK": "ThikDik", @@ -16366,6 +16512,7 @@ "TIKI": "Tiki Token", "TIKTOK": "Tiktok", "TIKTOKEN": "TikToken", + "TILECOIN": "TileCoin", "TIM": "TIMTIM GAMES", "TIME": "Chrono.tech", "TIMEFUN": "timefun", @@ -16692,6 +16839,7 @@ "TRR": "Terran Coin", "TRSCT": "Transactra Finance", "TRST": "TrustCoin", + "TRT": "TRUST AI", "TRTL": "TurtleCoin", "TRTT": "Trittium", "TRU": "TrueFi", @@ -16790,6 +16938,7 @@ "TSLAON": "Tesla (Ondo Tokenized)", "TSLAX": "Tesla xStock", "TSLT": "Tamkin", + "TSMON": "Taiwan Semiconductor Manufacturing (Ondo Tokenized)", "TSN": "Tsunami Exchange Token", "TSO": "Thesirion", "TSOTCHKE": "tsotchke", @@ -16847,6 +16996,7 @@ "TUTC": "TUTUT COIN", "TUTELLUS": "Tutellus", "TUTTER": "Tutter", + "TUURNT": "TuurnT", "TUX": "Tux The Penguin", "TUZKI": "Tuzki", "TUZLA": "Tuzlaspor Token", @@ -17161,6 +17311,7 @@ "USDCSO": "USD Coin (Portal from Solana)", "USDCV": "USD CoinVertible", "USDD": "USDD", + "USDDD": "USDDD", "USDDV1": "USDD v1", "USDE": "Ethena USDe", "USDEBT": "USDEBT", @@ -17169,6 +17320,7 @@ "USDFL": "USDFreeLiquidity", "USDG": "Global Dollar", "USDGLOBI": "Globiance USD Stablecoin", + "USDGO": "USDGO", "USDGV1": "USDG v1", "USDGV2": "USDG", "USDH": "USDH", @@ -17181,8 +17333,10 @@ "USDL": "Lift Dollar", "USDM": "USDM", "USDMA": "USD mars", - "USDN": "Neutral AI", + "USDN": "Ultimate Synthetic Delta Neutral", + "USDNEUTRAL": "Neutral AI", "USDO": "USD Open Dollar", + "USDON": "U.S. Dollar Tokenized Currency (Ondo)", "USDP": "Pax Dollar", "USDPLUS": "Overnight.fi USD+", "USDQ": "Quantoz USDQ", @@ -17191,6 +17345,7 @@ "USDS": "Sky Dollar", "USDSB": "USDSB", "USDSTABLY": "StableUSD", + "USDSUI": "USDsui", "USDT": "Tether", "USDT0": "USDT0", "USDT1": "USDT1", @@ -17293,6 +17448,7 @@ "VALENTINE": "Valentine", "VALI": "VALIMARKET", "VALID": "Validator Token", + "VALLEYDAO": "ValleyDAO Token", "VALOR": "Valor Token", "VALORBIT": "Valorbit", "VALU": "Value", @@ -17363,6 +17519,7 @@ "VEE": "BLOCKv", "VEED": "VEED", "VEEN": "LIVEEN", + "VEETOKEN": "Vee Token", "VEG": "BitVegan", "VEGA": "Vega Protocol", "VEGAS": "Vegas", @@ -17380,6 +17537,7 @@ "VELODV1": "Velodrome v1", "VELOX": "Velox", "VELOXPROJECT": "Velox", + "VELT": "VELTRIXA", "VELVET": "Velvet", "VEMP": "vEmpire DDAO", "VEN": "VeChain Old", @@ -17442,6 +17600,7 @@ "VICA": "ViCA Token", "VICE": "VICE Token", "VICEX": "ViceToken", + "VICPAY": "VICTORUM", "VICS": "RoboF", "VICT": "Victory Impact Coin", "VICTORIUM": "Victorium", @@ -17456,6 +17615,7 @@ "VIDZ": "PureVidz", "VIEW": "Viewly", "VIG": "TheVig", + "VIGI": "Vigi", "VIK": "VIKTAMA", "VIKITA": "VIKITA", "VIKKY": "VikkyToken", @@ -17476,6 +17636,7 @@ "VIRTUALMINING": "VirtualMining Coin", "VIRTUM": "VIRTUMATE", "VIS": "Vigorus", + "VISAON": "Visa (Ondo Tokenized)", "VISIO": "Visio", "VISION": "VisionGame", "VISIONCITY": "Vision City", @@ -17513,6 +17674,7 @@ "VLC": "Volcano Uni", "VLDY": "Validity", "VLK": "Vulkania", + "VLR": "Velora", "VLS": "Veles", "VLT": "Veltor", "VLTC": "Venus LTC", @@ -17733,6 +17895,7 @@ "WANUSDT": "wanUSDT", "WAP": "Wet Ass Pussy", "WAR": "WAR", + "WARD": "Warden", "WARP": "WarpCoin", "WARPED": "Warped Games", "WARPIE": "Warpie", @@ -17844,6 +18007,7 @@ "WEFI": "WeFi", "WEGEN": "WeGen Platform", "WEGI": "Wegie", + "WEGL": "White Eagle", "WEGLD": "Wrapped EGLD", "WEHMND": "Wrapped eHMND", "WEHODL": "HODL", @@ -18057,6 +18221,7 @@ "WMXWOM": "Wombex WOM", "WNCG": "Wrapped NCG", "WND": "WonderHero", + "WNDGAME": "Wizards And Dragons", "WNDR": "Wonderman Nation", "WNE": "Winee3", "WNEAR": "Wrapped Near", @@ -18125,6 +18290,7 @@ "WORLDLIBERTYICU": "World Liberty Financial", "WORLDLIBERTYSOL": "World Liberty Financial", "WORLDOFD": "World of Defish", + "WORLDW": "World War 3", "WORM": "HealthyWorm", "WORX": "Worx", "WOS": "Wolf Of Solana", @@ -18336,6 +18502,7 @@ "XCPO": "Copico", "XCR": "Crypti", "XCRE": "Creatio", + "XCREDI": "xCREDI", "XCRX": "xCRX", "XCT": "C-Bits", "XCUR": "Curate", @@ -18419,6 +18586,7 @@ "XIL": "Xillion", "XIN": "Mixin", "XING": "Xing Xing", + "XINGXING": "星星", "XINU": "XINU", "XIO": "Blockzero Labs", "XION": "XION", @@ -18494,6 +18662,7 @@ "XP": "Xphere", "XPA": "XPA", "XPARTY": "X Party", + "XPASS": "XPASS Token", "XPAT": "Bitnation Pangea", "XPAY": "Wallet Pay", "XPB": "Pebble Coin", @@ -18587,7 +18756,7 @@ "XT3": "Xt3ch", "XTAG": "xHashtag", "XTAL": "XTAL", - "XTC": "TileCoin", + "XTC": "Xitcoin", "XTECH": "X-TECH", "XTER": "Xterio", "XTK": "xToken", @@ -18723,6 +18892,7 @@ "YFL": "YF Link", "YFO": "YFIONE", "YFPRO": "YFPRO Finance", + "YFSX": "YFSX", "YFTE": "YFTether", "YFV": "YFValue", "YFX": "Your Futures Exchange", @@ -18869,8 +19039,7 @@ "ZEBU": "ZEBU", "ZEC": "ZCash", "ZECD": "ZCashDarkCoin", - "ZED": "ZED Token", - "ZEDCOIN": "ZedCoin", + "ZED": "ZedCoins", "ZEDD": "ZedDex", "ZEDTOKEN": "Zed Token", "ZEDX": "ZEDX Сoin", @@ -18928,6 +19097,7 @@ "ZFL": "Zuflo Coin", "ZFLOKI": "zkFloki", "ZFM": "ZFMCOIN", + "ZGC": "Z Generation Coin", "ZGD": "ZambesiGold", "ZGEM": "GemSwap", "ZHC": "ZHC : Zero Hour Cash", @@ -19105,10 +19275,13 @@ "分红狗头": "分红狗头", "哭哭马": "哭哭马", "安": "安", + "小龙虾": "币安小龙虾", "币安人生": "币安人生", "恶俗企鹅": "恶俗企鹅", "我踏马来了": "我踏马来了", + "狗屎": "狗屎", "老子": "老子", "雪球": "雪球", - "黑马": "黑马" + "黑马": "黑马", + "龙虾": "龙虾" } diff --git a/apps/api/src/assets/cryptocurrencies/custom.json b/apps/api/src/assets/cryptocurrencies/custom.json index 814aeec34..a26fc33df 100644 --- a/apps/api/src/assets/cryptocurrencies/custom.json +++ b/apps/api/src/assets/cryptocurrencies/custom.json @@ -4,6 +4,7 @@ "LUNA1": "Terra", "LUNA2": "Terra", "SGB1": "Songbird", + "SKY33038": "Sky", "SMURFCAT": "Real Smurf Cat", "TON11419": "Toncoin", "UNI1": "Uniswap", diff --git a/apps/api/src/events/asset-profile-changed.event.ts b/apps/api/src/events/asset-profile-changed.event.ts index 46a8c5db4..c08fe59f1 100644 --- a/apps/api/src/events/asset-profile-changed.event.ts +++ b/apps/api/src/events/asset-profile-changed.event.ts @@ -8,4 +8,16 @@ export class AssetProfileChangedEvent { public static getName(): string { return 'assetProfile.changed'; } + + public getCurrency() { + return this.data.currency; + } + + public getDataSource() { + return this.data.dataSource; + } + + public getSymbol() { + return this.data.symbol; + } } diff --git a/apps/api/src/events/asset-profile-changed.listener.ts b/apps/api/src/events/asset-profile-changed.listener.ts index ad80ee4a5..cc70edad6 100644 --- a/apps/api/src/events/asset-profile-changed.listener.ts +++ b/apps/api/src/events/asset-profile-changed.listener.ts @@ -1,29 +1,74 @@ -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; +import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { Injectable, Logger } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; +import { DataSource } from '@prisma/client'; +import ms from 'ms'; import { AssetProfileChangedEvent } from './asset-profile-changed.event'; @Injectable() export class AssetProfileChangedListener { + private static readonly DEBOUNCE_DELAY = ms('5 seconds'); + + private debounceTimers = new Map(); + public constructor( + private readonly activitiesService: ActivitiesService, private readonly configurationService: ConfigurationService, private readonly dataGatheringService: DataGatheringService, private readonly dataProviderService: DataProviderService, - private readonly exchangeRateDataService: ExchangeRateDataService, - private readonly orderService: OrderService + private readonly exchangeRateDataService: ExchangeRateDataService ) {} @OnEvent(AssetProfileChangedEvent.getName()) - public async handleAssetProfileChanged(event: AssetProfileChangedEvent) { + public handleAssetProfileChanged(event: AssetProfileChangedEvent) { + const currency = event.getCurrency(); + const dataSource = event.getDataSource(); + const symbol = event.getSymbol(); + + const key = getAssetProfileIdentifier({ + dataSource, + symbol + }); + + const existingTimer = this.debounceTimers.get(key); + + if (existingTimer) { + clearTimeout(existingTimer); + } + + this.debounceTimers.set( + key, + setTimeout(() => { + this.debounceTimers.delete(key); + + void this.processAssetProfileChanged({ + currency, + dataSource, + symbol + }); + }, AssetProfileChangedListener.DEBOUNCE_DELAY) + ); + } + + private async processAssetProfileChanged({ + currency, + dataSource, + symbol + }: { + currency: string; + dataSource: DataSource; + symbol: string; + }) { Logger.log( - `Asset profile of ${event.data.symbol} (${event.data.dataSource}) has changed`, + `Asset profile of ${symbol} (${dataSource}) has changed`, 'AssetProfileChangedListener' ); @@ -31,16 +76,16 @@ export class AssetProfileChangedListener { this.configurationService.get( 'ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES' ) === false || - event.data.currency === DEFAULT_CURRENCY + currency === DEFAULT_CURRENCY ) { return; } const existingCurrencies = this.exchangeRateDataService.getCurrencies(); - if (!existingCurrencies.includes(event.data.currency)) { + if (!existingCurrencies.includes(currency)) { Logger.log( - `New currency ${event.data.currency} has been detected`, + `New currency ${currency} has been detected`, 'AssetProfileChangedListener' ); @@ -48,13 +93,13 @@ export class AssetProfileChangedListener { } const { dateOfFirstActivity } = - await this.orderService.getStatisticsByCurrency(event.data.currency); + await this.activitiesService.getStatisticsByCurrency(currency); if (dateOfFirstActivity) { await this.dataGatheringService.gatherSymbol({ dataSource: this.dataProviderService.getDataSourceForExchangeRates(), date: dateOfFirstActivity, - symbol: `${DEFAULT_CURRENCY}${event.data.currency}` + symbol: `${DEFAULT_CURRENCY}${currency}` }); } } diff --git a/apps/api/src/events/events.module.ts b/apps/api/src/events/events.module.ts index ece67ebe0..772766945 100644 --- a/apps/api/src/events/events.module.ts +++ b/apps/api/src/events/events.module.ts @@ -1,4 +1,4 @@ -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; @@ -12,11 +12,11 @@ import { PortfolioChangedListener } from './portfolio-changed.listener'; @Module({ imports: [ + ActivitiesModule, ConfigurationModule, DataGatheringModule, DataProviderModule, ExchangeRateDataModule, - OrderModule, RedisCacheModule ], providers: [AssetProfileChangedListener, PortfolioChangedListener] diff --git a/apps/api/src/events/portfolio-changed.listener.ts b/apps/api/src/events/portfolio-changed.listener.ts index d12b9558d..f8e2a9229 100644 --- a/apps/api/src/events/portfolio-changed.listener.ts +++ b/apps/api/src/events/portfolio-changed.listener.ts @@ -2,22 +2,44 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.s import { Injectable, Logger } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; +import ms from 'ms'; import { PortfolioChangedEvent } from './portfolio-changed.event'; @Injectable() export class PortfolioChangedListener { + private static readonly DEBOUNCE_DELAY = ms('5 seconds'); + + private debounceTimers = new Map(); + public constructor(private readonly redisCacheService: RedisCacheService) {} @OnEvent(PortfolioChangedEvent.getName()) handlePortfolioChangedEvent(event: PortfolioChangedEvent) { + const userId = event.getUserId(); + + const existingTimer = this.debounceTimers.get(userId); + + if (existingTimer) { + clearTimeout(existingTimer); + } + + this.debounceTimers.set( + userId, + setTimeout(() => { + this.debounceTimers.delete(userId); + + void this.processPortfolioChanged({ userId }); + }, PortfolioChangedListener.DEBOUNCE_DELAY) + ); + } + + private async processPortfolioChanged({ userId }: { userId: string }) { Logger.log( - `Portfolio of user '${event.getUserId()}' has changed`, + `Portfolio of user '${userId}' has changed`, 'PortfolioChangedListener' ); - this.redisCacheService.removePortfolioSnapshotsByUserId({ - userId: event.getUserId() - }); + await this.redisCacheService.removePortfolioSnapshotsByUserId({ userId }); } } diff --git a/apps/api/src/helper/object.helper.spec.ts b/apps/api/src/helper/object.helper.spec.ts index ed821390f..ba8760c70 100644 --- a/apps/api/src/helper/object.helper.spec.ts +++ b/apps/api/src/helper/object.helper.spec.ts @@ -1540,7 +1540,6 @@ describe('redactAttributes', () => { netPerformanceWithCurrencyEffect: null, totalBuy: null, totalSell: null, - committedFunds: null, currentValueInBaseCurrency: null, dividendInBaseCurrency: null, emergencyFund: null, @@ -1554,6 +1553,7 @@ describe('redactAttributes', () => { items: null, liabilities: null, totalInvestment: null, + totalInvestmentValueWithCurrencyEffect: null, totalValueInBaseCurrency: null, currentNetWorth: null } @@ -3016,7 +3016,6 @@ describe('redactAttributes', () => { netPerformanceWithCurrencyEffect: null, totalBuy: null, totalSell: null, - committedFunds: null, currentValueInBaseCurrency: null, dividendInBaseCurrency: null, emergencyFund: null, @@ -3030,6 +3029,7 @@ describe('redactAttributes', () => { items: null, liabilities: null, totalInvestment: null, + totalInvestmentValueWithCurrencyEffect: null, totalValueInBaseCurrency: null, currentNetWorth: null } diff --git a/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts b/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts index eaa6dd08c..57643f76c 100644 --- a/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts +++ b/apps/api/src/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor.ts @@ -62,10 +62,13 @@ export class TransformDataSourceInResponseInterceptor< valueMap, object: data, paths: [ + 'activities[*].dataSource', 'activities[*].SymbolProfile.dataSource', 'benchmarks[*].dataSource', + 'errors[*].dataSource', 'fearAndGreedIndex.CRYPTOCURRENCIES.dataSource', 'fearAndGreedIndex.STOCKS.dataSource', + 'holdings[*].assetProfile.dataSource', 'holdings[*].dataSource', 'items[*].dataSource', 'SymbolProfile.dataSource', diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index a8de3dc5e..f08a09a83 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,4 +1,5 @@ import { + BULL_BOARD_ROUTE, DEFAULT_HOST, DEFAULT_PORT, STORYBOOK_PATH, @@ -14,6 +15,7 @@ import { import { ConfigService } from '@nestjs/config'; import { NestFactory } from '@nestjs/core'; import type { NestExpressApplication } from '@nestjs/platform-express'; +import cookieParser from 'cookie-parser'; import { NextFunction, Request, Response } from 'express'; import helmet from 'helmet'; @@ -46,6 +48,7 @@ async function bootstrap() { }); app.setGlobalPrefix('api', { exclude: [ + `${BULL_BOARD_ROUTE.substring(1)}{/*wildcard}`, 'sitemap.xml', ...SUPPORTED_LANGUAGE_CODES.map((languageCode) => { // Exclude language-specific routes with an optional wildcard @@ -53,6 +56,7 @@ async function bootstrap() { }) ] }); + app.useGlobalPipes( new ValidationPipe({ forbidNonWhitelisted: true, @@ -64,6 +68,8 @@ async function bootstrap() { // Support 10mb csv/json files for importing activities app.useBodyParser('json', { limit: '10mb' }); + app.use(cookieParser()); + if (configService.get('ENABLE_FEATURE_SUBSCRIPTION') === 'true') { app.use((req: Request, res: Response, next: NextFunction) => { if (req.path.startsWith(STORYBOOK_PATH)) { diff --git a/apps/api/src/middlewares/bull-board-auth.middleware.ts b/apps/api/src/middlewares/bull-board-auth.middleware.ts new file mode 100644 index 000000000..432deb974 --- /dev/null +++ b/apps/api/src/middlewares/bull-board-auth.middleware.ts @@ -0,0 +1,28 @@ +import { BULL_BOARD_COOKIE_NAME } from '@ghostfolio/common/config'; +import { hasPermission, permissions } from '@ghostfolio/common/permissions'; + +import { ForbiddenException, Injectable, NestMiddleware } from '@nestjs/common'; +import { NextFunction, Request, Response } from 'express'; +import passport from 'passport'; + +@Injectable() +export class BullBoardAuthMiddleware implements NestMiddleware { + public use(req: Request, res: Response, next: NextFunction) { + const token = req.cookies?.[BULL_BOARD_COOKIE_NAME]; + + if (token) { + req.headers.authorization = `Bearer ${token}`; + } + + passport.authenticate('jwt', { session: false }, (error, user) => { + if ( + error || + !hasPermission(user?.permissions, permissions.accessAdminControl) + ) { + next(new ForbiddenException()); + } else { + next(); + } + })(req, res, next); + } +} diff --git a/apps/api/src/models/rule.ts b/apps/api/src/models/rule.ts index 9c27e0018..5603964c5 100644 --- a/apps/api/src/models/rule.ts +++ b/apps/api/src/models/rule.ts @@ -57,7 +57,7 @@ export abstract class Rule implements RuleInterface { previousValue + this.exchangeRateDataService.toCurrency( new Big(currentValue.quantity) - .mul(currentValue.marketPrice) + .mul(currentValue.marketPrice ?? 0) .toNumber(), currentValue.currency, baseCurrency @@ -70,8 +70,6 @@ export abstract class Rule implements RuleInterface { public abstract evaluate(aRuleSettings: T): EvaluationResult; - public abstract getCategoryName(): string; - public abstract getConfiguration(): Partial< PortfolioReportRule['configuration'] >; diff --git a/apps/api/src/models/rules/account-cluster-risk/current-investment.ts b/apps/api/src/models/rules/account-cluster-risk/current-investment.ts index 0004d394e..400a2506f 100644 --- a/apps/api/src/models/rules/account-cluster-risk/current-investment.ts +++ b/apps/api/src/models/rules/account-cluster-risk/current-investment.ts @@ -98,13 +98,6 @@ export class AccountClusterRiskCurrentInvestment extends Rule { }; } - public getCategoryName() { - return this.i18nService.getTranslation({ - id: 'rule.accountClusterRisk.category', - languageCode: this.getLanguageCode() - }); - } - public getConfiguration() { return { threshold: { diff --git a/apps/api/src/models/rules/account-cluster-risk/single-account.ts b/apps/api/src/models/rules/account-cluster-risk/single-account.ts index 9988ea3cc..e4ee99064 100644 --- a/apps/api/src/models/rules/account-cluster-risk/single-account.ts +++ b/apps/api/src/models/rules/account-cluster-risk/single-account.ts @@ -57,13 +57,6 @@ export class AccountClusterRiskSingleAccount extends Rule { }; } - public getCategoryName() { - return this.i18nService.getTranslation({ - id: 'rule.accountClusterRisk.category', - languageCode: this.getLanguageCode() - }); - } - public getConfiguration() { return undefined; } diff --git a/apps/api/src/models/rules/asset-class-cluster-risk/equity.ts b/apps/api/src/models/rules/asset-class-cluster-risk/equity.ts index f70756e91..372b0bb06 100644 --- a/apps/api/src/models/rules/asset-class-cluster-risk/equity.ts +++ b/apps/api/src/models/rules/asset-class-cluster-risk/equity.ts @@ -85,13 +85,6 @@ export class AssetClassClusterRiskEquity extends Rule { }; } - public getCategoryName() { - return this.i18nService.getTranslation({ - id: 'rule.assetClassClusterRisk.category', - languageCode: this.getLanguageCode() - }); - } - public getConfiguration() { return { threshold: { diff --git a/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts b/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts index 3bd835e4d..404b4cd32 100644 --- a/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts +++ b/apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts @@ -85,13 +85,6 @@ export class AssetClassClusterRiskFixedIncome extends Rule { }; } - public getCategoryName() { - return this.i18nService.getTranslation({ - id: 'rule.assetClassClusterRisk.category', - languageCode: this.getLanguageCode() - }); - } - public getConfiguration() { return { threshold: { diff --git a/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts b/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts index d3176582f..83c72eeb8 100644 --- a/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts +++ b/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts @@ -82,13 +82,6 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule { }; } - public getCategoryName() { - return this.i18nService.getTranslation({ - id: 'rule.currencyClusterRisk.category', - languageCode: this.getLanguageCode() - }); - } - public getConfiguration() { return { threshold: { diff --git a/apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts b/apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts index df9b78eef..70f09f58c 100644 --- a/apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts +++ b/apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts @@ -76,13 +76,6 @@ export class EconomicMarketClusterRiskDevelopedMarkets extends Rule { }; } - public getCategoryName() { - return this.i18nService.getTranslation({ - id: 'rule.economicMarketClusterRisk.category', - languageCode: this.getLanguageCode() - }); - } - public getConfiguration() { return { threshold: { diff --git a/apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts b/apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts index 4583dc50a..120c3f6a2 100644 --- a/apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts +++ b/apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts @@ -76,13 +76,6 @@ export class EconomicMarketClusterRiskEmergingMarkets extends Rule { }; } - public getCategoryName() { - return this.i18nService.getTranslation({ - id: 'rule.economicMarketClusterRisk.category', - languageCode: this.getLanguageCode() - }); - } - public getConfiguration() { return { threshold: { diff --git a/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts b/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts index b956263f8..fcbd99d54 100644 --- a/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts +++ b/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts @@ -40,13 +40,6 @@ export class EmergencyFundSetup extends Rule { }; } - public getCategoryName() { - return this.i18nService.getTranslation({ - id: 'rule.emergencyFund.category', - languageCode: this.getLanguageCode() - }); - } - public getConfiguration() { return undefined; } diff --git a/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts b/apps/api/src/models/rules/fees/fee-ratio-total-investment-volume.ts similarity index 76% rename from apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts rename to apps/api/src/models/rules/fees/fee-ratio-total-investment-volume.ts index cb85a73ba..23f9076e8 100644 --- a/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts +++ b/apps/api/src/models/rules/fees/fee-ratio-total-investment-volume.ts @@ -3,35 +3,36 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service'; import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces'; -export class FeeRatioInitialInvestment extends Rule { +export class FeeRatioTotalInvestmentVolume extends Rule { private fees: number; - private totalInvestment: number; + private totalInvestmentVolumeInBaseCurrency: number; public constructor( protected exchangeRateDataService: ExchangeRateDataService, private i18nService: I18nService, languageCode: string, - totalInvestment: number, + totalInvestmentVolumeInBaseCurrency: number, fees: number ) { super(exchangeRateDataService, { languageCode, - key: FeeRatioInitialInvestment.name + key: FeeRatioTotalInvestmentVolume.name }); this.fees = fees; - this.totalInvestment = totalInvestment; + this.totalInvestmentVolumeInBaseCurrency = + totalInvestmentVolumeInBaseCurrency; } public evaluate(ruleSettings: Settings) { - const feeRatio = this.totalInvestment - ? this.fees / this.totalInvestment + const feeRatio = this.totalInvestmentVolumeInBaseCurrency + ? this.fees / this.totalInvestmentVolumeInBaseCurrency : 0; if (feeRatio > ruleSettings.thresholdMax) { return { evaluation: this.i18nService.getTranslation({ - id: 'rule.feeRatioInitialInvestment.false', + id: 'rule.feeRatioTotalInvestmentVolume.false', languageCode: this.getLanguageCode(), placeholders: { feeRatio: (ruleSettings.thresholdMax * 100).toFixed(2), @@ -44,7 +45,7 @@ export class FeeRatioInitialInvestment extends Rule { return { evaluation: this.i18nService.getTranslation({ - id: 'rule.feeRatioInitialInvestment.true', + id: 'rule.feeRatioTotalInvestmentVolume.true', languageCode: this.getLanguageCode(), placeholders: { feeRatio: (feeRatio * 100).toPrecision(3), @@ -55,13 +56,6 @@ export class FeeRatioInitialInvestment extends Rule { }; } - public getCategoryName() { - return this.i18nService.getTranslation({ - id: 'rule.fees.category', - languageCode: this.getLanguageCode() - }); - } - public getConfiguration() { return { threshold: { @@ -76,7 +70,7 @@ export class FeeRatioInitialInvestment extends Rule { public getName() { return this.i18nService.getTranslation({ - id: 'rule.feeRatioInitialInvestment', + id: 'rule.feeRatioTotalInvestmentVolume', languageCode: this.getLanguageCode() }); } diff --git a/apps/api/src/models/rules/liquidity/buying-power.ts b/apps/api/src/models/rules/liquidity/buying-power.ts index 541750d7e..7e8b96143 100644 --- a/apps/api/src/models/rules/liquidity/buying-power.ts +++ b/apps/api/src/models/rules/liquidity/buying-power.ts @@ -63,13 +63,6 @@ export class BuyingPower extends Rule { }; } - public getCategoryName() { - return this.i18nService.getTranslation({ - id: 'rule.liquidity.category', - languageCode: this.getLanguageCode() - }); - } - public getConfiguration() { return { threshold: { diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts b/apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts index 1242df759..4723389b0 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/asia-pacific.ts @@ -70,10 +70,6 @@ export class RegionalMarketClusterRiskAsiaPacific extends Rule { }; } - public getCategoryName() { - return 'Regional Market Cluster Risk'; // TODO: Replace hardcoded text with i18n translation - } - public getConfiguration() { return { threshold: { diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts b/apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts index 8486d843b..d4695406a 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts @@ -72,10 +72,6 @@ export class RegionalMarketClusterRiskEmergingMarkets extends Rule { }; } - public getCategoryName() { - return 'Regional Market Cluster Risk'; // TODO: Replace hardcoded text with i18n translation - } - public getConfiguration() { return { threshold: { diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/europe.ts b/apps/api/src/models/rules/regional-market-cluster-risk/europe.ts index 459848db4..c5cb4d134 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/europe.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/europe.ts @@ -70,10 +70,6 @@ export class RegionalMarketClusterRiskEurope extends Rule { }; } - public getCategoryName() { - return 'Regional Market Cluster Risk'; // TODO: Replace hardcoded text with i18n translation - } - public getConfiguration() { return { threshold: { diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/japan.ts b/apps/api/src/models/rules/regional-market-cluster-risk/japan.ts index d9c1cff6b..fc9ab92ee 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/japan.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/japan.ts @@ -70,10 +70,6 @@ export class RegionalMarketClusterRiskJapan extends Rule { }; } - public getCategoryName() { - return 'Regional Market Cluster Risk'; // TODO: Replace hardcoded text with i18n translation - } - public getConfiguration() { return { threshold: { diff --git a/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts b/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts index 6180a2cc5..8bd3fb0cf 100644 --- a/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts +++ b/apps/api/src/models/rules/regional-market-cluster-risk/north-america.ts @@ -70,10 +70,6 @@ export class RegionalMarketClusterRiskNorthAmerica extends Rule { }; } - public getCategoryName() { - return 'Regional Market Cluster Risk'; // TODO: Replace hardcoded text with i18n translation - } - public getConfiguration() { return { threshold: { diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts index 5f9d1055d..ad8e84a99 100644 --- a/apps/api/src/services/configuration/configuration.service.ts +++ b/apps/api/src/services/configuration/configuration.service.ts @@ -30,6 +30,7 @@ export class ConfigurationService { API_KEY_FINANCIAL_MODELING_PREP: str({ default: '' }), API_KEY_OPEN_FIGI: str({ default: '' }), API_KEY_RAPID_API: str({ default: '' }), + BULL_BOARD_IS_READ_ONLY: bool({ default: true }), CACHE_QUOTES_TTL: num({ default: ms('1 minute') }), CACHE_TTL: num({ default: CACHE_TTL_NO_CACHE }), DATA_SOURCE_EXCHANGE_RATES: str({ default: DataSource.YAHOO }), @@ -43,6 +44,7 @@ export class ConfigurationService { ENABLE_FEATURE_AUTH_GOOGLE: bool({ default: false }), ENABLE_FEATURE_AUTH_OIDC: bool({ default: false }), ENABLE_FEATURE_AUTH_TOKEN: bool({ default: true }), + ENABLE_FEATURE_BULL_BOARD: bool({ default: false }), ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }), ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES: bool({ default: true }), ENABLE_FEATURE_READ_ONLY_MODE: bool({ default: false }), diff --git a/apps/api/src/services/cryptocurrency/cryptocurrency.module.ts b/apps/api/src/services/cryptocurrency/cryptocurrency.module.ts index 8820205eb..e882f4da5 100644 --- a/apps/api/src/services/cryptocurrency/cryptocurrency.module.ts +++ b/apps/api/src/services/cryptocurrency/cryptocurrency.module.ts @@ -1,9 +1,12 @@ +import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; + import { Module } from '@nestjs/common'; import { CryptocurrencyService } from './cryptocurrency.service'; @Module({ - providers: [CryptocurrencyService], - exports: [CryptocurrencyService] + exports: [CryptocurrencyService], + imports: [PropertyModule], + providers: [CryptocurrencyService] }) export class CryptocurrencyModule {} diff --git a/apps/api/src/services/cryptocurrency/cryptocurrency.service.ts b/apps/api/src/services/cryptocurrency/cryptocurrency.service.ts index b814fc186..933029ea2 100644 --- a/apps/api/src/services/cryptocurrency/cryptocurrency.service.ts +++ b/apps/api/src/services/cryptocurrency/cryptocurrency.service.ts @@ -1,31 +1,39 @@ -import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; +import { PropertyService } from '@ghostfolio/api/services/property/property.service'; +import { + DEFAULT_CURRENCY, + PROPERTY_CUSTOM_CRYPTOCURRENCIES +} from '@ghostfolio/common/config'; -import { Injectable } from '@nestjs/common'; +import { Injectable, OnModuleInit } from '@nestjs/common'; const cryptocurrencies = require('../../assets/cryptocurrencies/cryptocurrencies.json'); const customCryptocurrencies = require('../../assets/cryptocurrencies/custom.json'); @Injectable() -export class CryptocurrencyService { +export class CryptocurrencyService implements OnModuleInit { private combinedCryptocurrencies: string[]; + public constructor(private readonly propertyService: PropertyService) {} + + public async onModuleInit() { + const customCryptocurrenciesFromDatabase = + await this.propertyService.getByKey>( + PROPERTY_CUSTOM_CRYPTOCURRENCIES + ); + + this.combinedCryptocurrencies = [ + ...Object.keys(cryptocurrencies), + ...Object.keys(customCryptocurrencies), + ...Object.keys(customCryptocurrenciesFromDatabase ?? {}) + ]; + } + public isCryptocurrency(aSymbol = '') { const cryptocurrencySymbol = aSymbol.substring(0, aSymbol.length - 3); return ( aSymbol.endsWith(DEFAULT_CURRENCY) && - this.getCryptocurrencies().includes(cryptocurrencySymbol) + this.combinedCryptocurrencies.includes(cryptocurrencySymbol) ); } - - private getCryptocurrencies() { - if (!this.combinedCryptocurrencies) { - this.combinedCryptocurrencies = [ - ...Object.keys(cryptocurrencies), - ...Object.keys(customCryptocurrencies) - ]; - } - - return this.combinedCryptocurrencies; - } } diff --git a/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts b/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts index 6030e62d4..40b45a115 100644 --- a/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts +++ b/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts @@ -16,7 +16,7 @@ import { LookupResponse } from '@ghostfolio/common/interfaces'; -import { Injectable } from '@nestjs/common'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; import Alphavantage from 'alphavantage'; import { format, isAfter, isBefore, parse } from 'date-fns'; @@ -24,12 +24,16 @@ import { format, isAfter, isBefore, parse } from 'date-fns'; import { AlphaVantageHistoricalResponse } from './interfaces/interfaces'; @Injectable() -export class AlphaVantageService implements DataProviderInterface { +export class AlphaVantageService + implements DataProviderInterface, OnModuleInit +{ public alphaVantage; public constructor( private readonly configurationService: ConfigurationService - ) { + ) {} + + public onModuleInit() { this.alphaVantage = Alphavantage({ key: this.configurationService.get('API_KEY_ALPHA_VANTAGE') }); diff --git a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts index d0d96acac..d5ed69d06 100644 --- a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts +++ b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts @@ -17,7 +17,7 @@ import { LookupResponse } from '@ghostfolio/common/interfaces'; -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { AssetClass, AssetSubClass, @@ -27,13 +27,15 @@ import { import { format, fromUnixTime, getUnixTime } from 'date-fns'; @Injectable() -export class CoinGeckoService implements DataProviderInterface { - private readonly apiUrl: string; - private readonly headers: HeadersInit = {}; +export class CoinGeckoService implements DataProviderInterface, OnModuleInit { + private apiUrl: string; + private headers: HeadersInit = {}; public constructor( private readonly configurationService: ConfigurationService - ) { + ) {} + + public onModuleInit() { const apiKeyDemo = this.configurationService.get('API_KEY_COINGECKO_DEMO'); const apiKeyPro = this.configurationService.get('API_KEY_COINGECKO_PRO'); diff --git a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.spec.ts b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.spec.ts index c37a9fe3e..9335d86d0 100644 --- a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.spec.ts +++ b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.spec.ts @@ -29,7 +29,7 @@ describe('YahooFinanceDataEnhancerService', () => { let yahooFinanceDataEnhancerService: YahooFinanceDataEnhancerService; beforeAll(async () => { - cryptocurrencyService = new CryptocurrencyService(); + cryptocurrencyService = new CryptocurrencyService(null); yahooFinanceDataEnhancerService = new YahooFinanceDataEnhancerService( cryptocurrencyService diff --git a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts index c83e35503..72136dc04 100644 --- a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts +++ b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts @@ -207,14 +207,16 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface { if (['ETF', 'MUTUALFUND'].includes(assetSubClass)) { response.holdings = - assetProfile.topHoldings?.holdings?.map( - ({ holdingName, holdingPercent }) => { + assetProfile.topHoldings?.holdings + ?.filter(({ holdingName }) => { + return !holdingName?.includes('ETF'); + }) + ?.map(({ holdingName, holdingPercent }) => { return { name: this.formatName({ longName: holdingName }), weight: holdingPercent }; - } - ) ?? []; + }) ?? []; response.sectors = ( assetProfile.topHoldings?.sectorWeightings ?? [] diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index 5a088c0e4..a6b12cce2 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -1,3 +1,4 @@ +import { ImportDataDto } from '@ghostfolio/api/app/import/import-data.dto'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; @@ -10,8 +11,10 @@ import { PROPERTY_API_KEY_GHOSTFOLIO, PROPERTY_DATA_SOURCE_MAPPING } from '@ghostfolio/common/config'; +import { CreateOrderDto } from '@ghostfolio/common/dtos'; import { DATE_FORMAT, + getAssetProfileIdentifier, getCurrencyFromSymbol, getStartOfUtcDate, isCurrency, @@ -27,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'; @@ -185,6 +188,125 @@ export class DataProviderService implements OnModuleInit { return dataSources.sort(); } + public async validateActivities({ + activitiesDto, + assetProfilesWithMarketDataDto, + maxActivitiesToImport, + user + }: { + activitiesDto: Pick< + Partial, + 'currency' | 'dataSource' | 'symbol' | 'type' + >[]; + assetProfilesWithMarketDataDto?: ImportDataDto['assetProfiles']; + maxActivitiesToImport: number; + user: UserWithSettings; + }) { + if (activitiesDto?.length > maxActivitiesToImport) { + throw new Error(`Too many activities (${maxActivitiesToImport} at most)`); + } + + const assetProfiles: { + [assetProfileIdentifier: string]: Partial; + } = {}; + + const dataSources = await this.getDataSources(); + + for (const [ + index, + { currency, dataSource, symbol, type } + ] of activitiesDto.entries()) { + const activityPath = + maxActivitiesToImport === 1 ? 'activity' : `activities.${index}`; + + if (!dataSources.includes(dataSource)) { + throw new Error( + `${activityPath}.dataSource ("${dataSource}") is not valid` + ); + } + + if ( + this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && + user.subscription.type === 'Basic' + ) { + const dataProvider = this.getDataProvider(DataSource[dataSource]); + + if (dataProvider.getDataProviderInfo().isPremium) { + throw new Error( + `${activityPath}.dataSource ("${dataSource}") is not valid` + ); + } + } + + const assetProfileIdentifier = getAssetProfileIdentifier({ + dataSource, + symbol + }); + + if (!assetProfiles[assetProfileIdentifier]) { + if ( + (dataSource === DataSource.MANUAL && type === 'BUY') || + ['FEE', 'INTEREST', 'LIABILITY'].includes(type) + ) { + const assetProfileInImport = assetProfilesWithMarketDataDto?.find( + (assetProfile) => { + return ( + assetProfile.dataSource === dataSource && + assetProfile.symbol === symbol + ); + } + ); + + assetProfiles[assetProfileIdentifier] = { + currency, + dataSource, + symbol, + name: assetProfileInImport?.name ?? symbol + }; + + continue; + } + + let assetProfile: Partial = { currency }; + + try { + assetProfile = ( + await this.getAssetProfiles([ + { + dataSource, + symbol + } + ]) + )?.[symbol]; + } catch {} + + if (!assetProfile?.name) { + const assetProfileInImport = assetProfilesWithMarketDataDto?.find( + (profile) => { + return ( + profile.dataSource === dataSource && profile.symbol === symbol + ); + } + ); + + if (assetProfileInImport) { + Object.assign(assetProfile, assetProfileInImport); + } + } + + if (!assetProfile?.name) { + throw new Error( + `activities.${index}.symbol ("${symbol}") is not valid for the specified data source ("${dataSource}")` + ); + } + + assetProfiles[assetProfileIdentifier] = assetProfile; + } + } + + return assetProfiles; + } + public async getDividends({ dataSource, from, @@ -225,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; diff --git a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts index cd20fca44..8c718108c 100644 --- a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts +++ b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts @@ -22,7 +22,7 @@ import { } from '@ghostfolio/common/interfaces'; import { MarketState } from '@ghostfolio/common/types'; -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { AssetClass, AssetSubClass, @@ -33,14 +33,18 @@ import { addDays, format, isSameDay, isToday } from 'date-fns'; import { isNumber } from 'lodash'; @Injectable() -export class EodHistoricalDataService implements DataProviderInterface { +export class EodHistoricalDataService + implements DataProviderInterface, OnModuleInit +{ private apiKey: string; private readonly URL = 'https://eodhistoricaldata.com/api'; public constructor( private readonly configurationService: ConfigurationService, private readonly symbolProfileService: SymbolProfileService - ) { + ) {} + + public onModuleInit() { this.apiKey = this.configurationService.get('API_KEY_EOD_HISTORICAL_DATA'); } diff --git a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts index 2b4193af5..27391130e 100644 --- a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts +++ b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts @@ -23,7 +23,7 @@ import { } from '@ghostfolio/common/interfaces'; import { MarketState } from '@ghostfolio/common/types'; -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { AssetClass, AssetSubClass, @@ -44,7 +44,9 @@ import { import { uniqBy } from 'lodash'; @Injectable() -export class FinancialModelingPrepService implements DataProviderInterface { +export class FinancialModelingPrepService + implements DataProviderInterface, OnModuleInit +{ private static countriesMapping = { 'Korea (the Republic of)': 'South Korea', 'Russian Federation': 'Russia', @@ -57,7 +59,9 @@ export class FinancialModelingPrepService implements DataProviderInterface { private readonly configurationService: ConfigurationService, private readonly cryptocurrencyService: CryptocurrencyService, private readonly prismaService: PrismaService - ) { + ) {} + + public onModuleInit() { this.apiKey = this.configurationService.get( 'API_KEY_FINANCIAL_MODELING_PREP' ); diff --git a/apps/api/src/services/data-provider/manual/manual.service.ts b/apps/api/src/services/data-provider/manual/manual.service.ts index 7392f0914..51e65e631 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -105,7 +105,10 @@ export class ManualService implements DataProviderInterface { return {}; } - const value = await this.scrape(symbolProfile.scraperConfiguration); + const value = await this.scrape({ + symbol, + scraperConfiguration: symbolProfile.scraperConfiguration + }); return { [symbol]: { @@ -170,7 +173,10 @@ export class ManualService implements DataProviderInterface { symbolProfilesWithScraperConfigurationAndInstantMode.map( async ({ scraperConfiguration, symbol }) => { try { - const marketPrice = await this.scrape(scraperConfiguration); + const marketPrice = await this.scrape({ + scraperConfiguration, + symbol + }); return { marketPrice, symbol }; } catch (error) { Logger.error( @@ -267,13 +273,23 @@ export class ManualService implements DataProviderInterface { }; } - public async test(scraperConfiguration: ScraperConfiguration) { - return this.scrape(scraperConfiguration); + public async test({ + scraperConfiguration, + symbol + }: { + scraperConfiguration: ScraperConfiguration; + symbol: string; + }) { + return this.scrape({ scraperConfiguration, symbol }); } - private async scrape( - scraperConfiguration: ScraperConfiguration - ): Promise { + private async scrape({ + scraperConfiguration, + symbol + }: { + scraperConfiguration: ScraperConfiguration; + symbol: string; + }): Promise { let locale = scraperConfiguration.locale; const response = await fetch(scraperConfiguration.url, { @@ -283,6 +299,12 @@ export class ManualService implements DataProviderInterface { ) }); + if (!response.ok) { + throw new Error( + `Failed to scrape the market price for ${symbol} (${this.getName()}): ${response.status} ${response.statusText} at ${scraperConfiguration.url}` + ); + } + let value: string; if (response.headers.get('content-type')?.includes('application/json')) { diff --git a/apps/api/src/services/i18n/i18n.service.ts b/apps/api/src/services/i18n/i18n.service.ts index cf340d7c6..1cdb811a9 100644 --- a/apps/api/src/services/i18n/i18n.service.ts +++ b/apps/api/src/services/i18n/i18n.service.ts @@ -1,16 +1,16 @@ import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config'; -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import * as cheerio from 'cheerio'; import { readFileSync, readdirSync } from 'node:fs'; import { join } from 'node:path'; @Injectable() -export class I18nService { +export class I18nService implements OnModuleInit { private localesPath = join(__dirname, 'assets', 'locales'); private translations: { [locale: string]: cheerio.CheerioAPI } = {}; - public constructor() { + public onModuleInit() { this.loadFiles(); } diff --git a/apps/api/src/services/interfaces/environment.interface.ts b/apps/api/src/services/interfaces/environment.interface.ts index 57c58898e..9664ae144 100644 --- a/apps/api/src/services/interfaces/environment.interface.ts +++ b/apps/api/src/services/interfaces/environment.interface.ts @@ -10,6 +10,7 @@ export interface Environment extends CleanedEnvAccessors { API_KEY_FINANCIAL_MODELING_PREP: string; API_KEY_OPEN_FIGI: string; API_KEY_RAPID_API: string; + BULL_BOARD_IS_READ_ONLY: boolean; CACHE_QUOTES_TTL: number; CACHE_TTL: number; DATA_SOURCE_EXCHANGE_RATES: string; @@ -19,6 +20,7 @@ export interface Environment extends CleanedEnvAccessors { ENABLE_FEATURE_AUTH_GOOGLE: boolean; ENABLE_FEATURE_AUTH_OIDC: boolean; ENABLE_FEATURE_AUTH_TOKEN: boolean; + ENABLE_FEATURE_BULL_BOARD: boolean; ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean; ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES: boolean; ENABLE_FEATURE_READ_ONLY_MODE: boolean; diff --git a/apps/api/src/services/queues/data-gathering/data-gathering.module.ts b/apps/api/src/services/queues/data-gathering/data-gathering.module.ts index b51823476..f251c8d0c 100644 --- a/apps/api/src/services/queues/data-gathering/data-gathering.module.ts +++ b/apps/api/src/services/queues/data-gathering/data-gathering.module.ts @@ -9,6 +9,8 @@ import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathe import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; import { DATA_GATHERING_QUEUE } from '@ghostfolio/common/config'; +import { BullAdapter } from '@bull-board/api/bullAdapter'; +import { BullBoardModule } from '@bull-board/nestjs'; import { BullModule } from '@nestjs/bull'; import { Module } from '@nestjs/common'; import ms from 'ms'; @@ -17,6 +19,18 @@ import { DataGatheringProcessor } from './data-gathering.processor'; @Module({ imports: [ + ...(process.env.ENABLE_FEATURE_BULL_BOARD === 'true' + ? [ + BullBoardModule.forFeature({ + adapter: BullAdapter, + name: DATA_GATHERING_QUEUE, + options: { + displayName: 'Data Gathering', + readOnlyMode: process.env.BULL_BOARD_IS_READ_ONLY !== 'false' + } + }) + ] + : []), BullModule.registerQueue({ limiter: { duration: ms('4 seconds'), diff --git a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts index 958636334..1260f1cf0 100644 --- a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts +++ b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts @@ -1,5 +1,5 @@ import { AccountBalanceModule } from '@ghostfolio/api/app/account-balance/account-balance.module'; -import { OrderModule } from '@ghostfolio/api/app/order/order.module'; +import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; @@ -13,6 +13,8 @@ import { PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE } from '@ghostfolio/common/config'; +import { BullAdapter } from '@bull-board/api/bullAdapter'; +import { BullBoardModule } from '@bull-board/nestjs'; import { BullModule } from '@nestjs/bull'; import { Module } from '@nestjs/common'; @@ -22,6 +24,19 @@ import { PortfolioSnapshotProcessor } from './portfolio-snapshot.processor'; exports: [BullModule, PortfolioSnapshotService], imports: [ AccountBalanceModule, + ActivitiesModule, + ...(process.env.ENABLE_FEATURE_BULL_BOARD === 'true' + ? [ + BullBoardModule.forFeature({ + adapter: BullAdapter, + name: PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE, + options: { + displayName: 'Portfolio Snapshot Computation', + readOnlyMode: process.env.BULL_BOARD_IS_READ_ONLY !== 'false' + } + }) + ] + : []), BullModule.registerQueue({ name: PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE, settings: { @@ -36,7 +51,6 @@ import { PortfolioSnapshotProcessor } from './portfolio-snapshot.processor'; DataProviderModule, ExchangeRateDataModule, MarketDataModule, - OrderModule, RedisCacheModule ], providers: [ diff --git a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts index 58a0a8f8a..f3aa6e77e 100644 --- a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts +++ b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts @@ -1,5 +1,5 @@ import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; -import { OrderService } from '@ghostfolio/api/app/order/order.service'; +import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service'; import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; import { PortfolioSnapshotValue } from '@ghostfolio/api/app/portfolio/interfaces/snapshot-value.interface'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; @@ -23,9 +23,9 @@ import { PortfolioSnapshotQueueJob } from './interfaces/portfolio-snapshot-queue export class PortfolioSnapshotProcessor { public constructor( private readonly accountBalanceService: AccountBalanceService, + private readonly activitiesService: ActivitiesService, private readonly calculatorFactory: PortfolioCalculatorFactory, private readonly configurationService: ConfigurationService, - private readonly orderService: OrderService, private readonly redisCacheService: RedisCacheService ) {} @@ -47,7 +47,7 @@ export class PortfolioSnapshotProcessor { ); const { activities } = - await this.orderService.getOrdersForPortfolioCalculator({ + await this.activitiesService.getActivitiesForPortfolioCalculator({ filters: job.data.filters, userCurrency: job.data.userCurrency, userId: job.data.userId, diff --git a/apps/api/src/services/twitter-bot/twitter-bot.service.ts b/apps/api/src/services/twitter-bot/twitter-bot.service.ts index ee951820d..b424f7198 100644 --- a/apps/api/src/services/twitter-bot/twitter-bot.service.ts +++ b/apps/api/src/services/twitter-bot/twitter-bot.service.ts @@ -10,19 +10,21 @@ import { resolveMarketCondition } from '@ghostfolio/common/helper'; -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { isWeekend } from 'date-fns'; import { TwitterApi, TwitterApiReadWrite } from 'twitter-api-v2'; @Injectable() -export class TwitterBotService { +export class TwitterBotService implements OnModuleInit { private twitterClient: TwitterApiReadWrite; public constructor( private readonly benchmarkService: BenchmarkService, private readonly configurationService: ConfigurationService, private readonly symbolService: SymbolService - ) { + ) {} + + public onModuleInit() { this.twitterClient = new TwitterApi({ accessSecret: this.configurationService.get( 'TWITTER_ACCESS_TOKEN_SECRET' diff --git a/apps/client/proxy.conf.json b/apps/client/proxy.conf.json index a31371d9f..825965b92 100644 --- a/apps/client/proxy.conf.json +++ b/apps/client/proxy.conf.json @@ -1,4 +1,8 @@ { + "/admin/queues": { + "target": "http://0.0.0.0:3333", + "secure": false + }, "/api": { "target": "http://0.0.0.0:3333", "secure": false diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index a4af01124..201c2f994 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -10,12 +10,13 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, DOCUMENT, HostBinding, - Inject, - OnDestroy, + inject, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatDialog } from '@angular/material/dialog'; import { Title } from '@angular/platform-browser'; import { @@ -30,15 +31,13 @@ import { DataSource } from '@prisma/client'; import { addIcons } from 'ionicons'; import { openOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { filter, takeUntil } from 'rxjs/operators'; +import { filter } from 'rxjs/operators'; import { GfFooterComponent } from './components/footer/footer.component'; import { GfHeaderComponent } from './components/header/header.component'; import { GfHoldingDetailDialogComponent } from './components/holding-detail-dialog/holding-detail-dialog.component'; -import { HoldingDetailDialogParams } from './components/holding-detail-dialog/interfaces/interfaces'; +import { GfAppQueryParams } from './interfaces/interfaces'; import { ImpersonationStorageService } from './services/impersonation-storage.service'; -import { TokenStorageService } from './services/token-storage.service'; import { UserService } from './services/user/user.service'; @Component({ @@ -48,11 +47,7 @@ import { UserService } from './services/user/user.service'; styleUrls: ['./app.component.scss'], templateUrl: './app.component.html' }) -export class GfAppComponent implements OnDestroy, OnInit { - @HostBinding('class.has-info-message') get getHasMessage() { - return this.hasInfoMessage; - } - +export class GfAppComponent implements OnInit { public canCreateAccount: boolean; public currentRoute: string; public currentSubRoute: string; @@ -67,52 +62,54 @@ export class GfAppComponent implements OnDestroy, OnInit { public pageTitle: string; public routerLinkRegister = publicRoutes.register.routerLink; public showFooter = false; - public user: User; - - private unsubscribeSubject = new Subject(); - - public constructor( - private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService, - private deviceService: DeviceDetectorService, - private dialog: MatDialog, - @Inject(DOCUMENT) private document: Document, - private impersonationStorageService: ImpersonationStorageService, - private notificationService: NotificationService, - private route: ActivatedRoute, - private router: Router, - private title: Title, - private tokenStorageService: TokenStorageService, - private userService: UserService - ) { + public user: User | undefined; + + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + private readonly deviceService = inject(DeviceDetectorService); + private readonly dialog = inject(MatDialog); + private readonly document = inject(DOCUMENT); + private readonly impersonationStorageService = inject( + ImpersonationStorageService + ); + private readonly notificationService = inject(NotificationService); + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly title = inject(Title); + private readonly userService = inject(UserService); + + public constructor() { this.initializeTheme(); this.user = undefined; this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe((params) => { - if ( - params['dataSource'] && - params['holdingDetailDialog'] && - params['symbol'] - ) { - this.openHoldingDetailDialog({ - dataSource: params['dataSource'], - symbol: params['symbol'] - }); + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe( + ({ dataSource, holdingDetailDialog, symbol }: GfAppQueryParams) => { + if (dataSource && holdingDetailDialog && symbol) { + this.openHoldingDetailDialog({ + dataSource, + symbol + }); + } } - }); + ); addIcons({ openOutline }); } + @HostBinding('class.has-info-message') get getHasMessage() { + return this.hasInfoMessage; + } + public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.info = this.dataService.fetchInfo(); this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); @@ -131,7 +128,7 @@ export class GfAppComponent implements OnDestroy, OnInit { !this.currentSubRoute) || (this.currentRoute === internalRoutes.home.path && this.currentSubRoute === - internalRoutes.home.subRoutes.holdings.path) || + internalRoutes.home.subRoutes?.holdings.path) || (this.currentRoute === internalRoutes.portfolio.path && !this.currentSubRoute)) && this.user?.settings?.viewMode !== 'ZEN' @@ -144,18 +141,18 @@ export class GfAppComponent implements OnDestroy, OnInit { if ( (this.currentRoute === internalRoutes.home.path && this.currentSubRoute === - internalRoutes.home.subRoutes.holdings.path) || + internalRoutes.home.subRoutes?.holdings.path) || (this.currentRoute === internalRoutes.portfolio.path && !this.currentSubRoute) || (this.currentRoute === internalRoutes.portfolio.path && this.currentSubRoute === - internalRoutes.portfolio.subRoutes.activities.path) || + internalRoutes.portfolio.subRoutes?.activities.path) || (this.currentRoute === internalRoutes.portfolio.path && this.currentSubRoute === - internalRoutes.portfolio.subRoutes.allocations.path) || + internalRoutes.portfolio.subRoutes?.allocations.path) || (this.currentRoute === internalRoutes.zen.path && this.currentSubRoute === - internalRoutes.home.subRoutes.holdings.path) + internalRoutes.home.subRoutes?.holdings.path) ) { this.hasPermissionToChangeFilters = true; } else { @@ -201,7 +198,7 @@ export class GfAppComponent implements OnDestroy, OnInit { }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { this.user = state.user; @@ -226,31 +223,31 @@ export class GfAppComponent implements OnDestroy, OnInit { } public onClickSystemMessage() { - if (this.user.systemMessage.routerLink) { - this.router.navigate(this.user.systemMessage.routerLink); + const systemMessage = this.user?.systemMessage; + + if (!systemMessage) { + return; + } + + if (systemMessage.routerLink) { + void this.router.navigate(systemMessage.routerLink); } else { this.notificationService.alert({ - title: this.user.systemMessage.message + title: systemMessage.message }); } } public onCreateAccount() { - this.tokenStorageService.signOut(); + this.userService.signOut(); } public onSignOut() { - this.tokenStorageService.signOut(); - this.userService.remove(); + this.userService.signOut(); document.location.href = `/${document.documentElement.lang}`; } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private initializeTheme(userPreferredColorScheme?: ColorScheme) { const isDarkTheme = userPreferredColorScheme ? userPreferredColorScheme === 'DARK' @@ -274,14 +271,11 @@ export class GfAppComponent implements OnDestroy, OnInit { }) { this.userService .get() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; - const dialogRef = this.dialog.open< - GfHoldingDetailDialogComponent, - HoldingDetailDialogParams - >(GfHoldingDetailDialogComponent, { + const dialogRef = this.dialog.open(GfHoldingDetailDialogComponent, { autoFocus: false, data: { dataSource, @@ -296,15 +290,21 @@ export class GfAppComponent implements OnDestroy, OnInit { ), hasPermissionToCreateActivity: !this.hasImpersonationId && - hasPermission(this.user?.permissions, permissions.createOrder) && + hasPermission( + this.user?.permissions, + permissions.createActivity + ) && !this.user?.settings?.isRestrictedView, hasPermissionToReportDataGlitch: hasPermission( this.user?.permissions, permissions.reportDataGlitch ), - hasPermissionToUpdateOrder: + hasPermissionToUpdateActivity: !this.hasImpersonationId && - hasPermission(this.user?.permissions, permissions.updateOrder) && + hasPermission( + this.user?.permissions, + permissions.updateActivity + ) && !this.user?.settings?.isRestrictedView, locale: this.user?.settings?.locale }, @@ -314,9 +314,9 @@ export class GfAppComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { - this.router.navigate([], { + void this.router.navigate([], { queryParams: { dataSource: null, holdingDetailDialog: null, @@ -342,6 +342,6 @@ export class GfAppComponent implements OnDestroy, OnInit { this.document .querySelector('meta[name="theme-color"]') - .setAttribute('content', themeColor); + ?.setAttribute('content', themeColor); } } diff --git a/apps/client/src/app/components/access-table/access-table.component.html b/apps/client/src/app/components/access-table/access-table.component.html index cb41904d3..4283d7860 100644 --- a/apps/client/src/app/components/access-table/access-table.component.html +++ b/apps/client/src/app/components/access-table/access-table.component.html @@ -39,7 +39,7 @@ getPublicUrl(element.id) }}
- @if (user?.settings?.isExperimentalFeatures) { + @if (user()?.settings?.isExperimentalFeatures) {
GET {{ baseUrl }}/api/v1/public/{{ @@ -69,7 +69,7 @@ class="no-max-width" xPosition="before" > - @if (user?.settings?.isExperimentalFeatures) { + @if (user()?.settings?.isExperimentalFeatures) { } @if ( - user?.settings?.isExperimentalFeatures || element.type === 'PUBLIC' + user()?.settings?.isExperimentalFeatures || + element.type === 'PUBLIC' ) {
} @@ -100,7 +101,7 @@ - - + +
diff --git a/apps/client/src/app/components/access-table/access-table.component.ts b/apps/client/src/app/components/access-table/access-table.component.ts index 76548c45d..122b4f88b 100644 --- a/apps/client/src/app/components/access-table/access-table.component.ts +++ b/apps/client/src/app/components/access-table/access-table.component.ts @@ -7,11 +7,12 @@ import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard'; import { ChangeDetectionStrategy, Component, + computed, CUSTOM_ELEMENTS_SCHEMA, - EventEmitter, - Input, - OnChanges, - Output + effect, + inject, + input, + output } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatMenuModule } from '@angular/material/menu'; @@ -46,23 +47,32 @@ import ms from 'ms'; templateUrl: './access-table.component.html', styleUrls: ['./access-table.component.scss'] }) -export class GfAccessTableComponent implements OnChanges { - @Input() accesses: Access[]; - @Input() showActions: boolean; - @Input() user: User; - - @Output() accessDeleted = new EventEmitter(); - @Output() accessToUpdate = new EventEmitter(); - - public baseUrl = window.location.origin; - public dataSource: MatTableDataSource; - public displayedColumns = []; - - public constructor( - private clipboard: Clipboard, - private notificationService: NotificationService, - private snackBar: MatSnackBar - ) { +export class GfAccessTableComponent { + public readonly accesses = input.required(); + public readonly showActions = input(false); + public readonly user = input.required(); + + public readonly accessDeleted = output(); + public readonly accessToUpdate = output(); + + protected readonly baseUrl = window.location.origin; + protected readonly dataSource = new MatTableDataSource(); + + protected readonly displayedColumns = computed(() => { + const columns = ['alias', 'grantee', 'type', 'details']; + + if (this.showActions()) { + columns.push('actions'); + } + + return columns; + }); + + private readonly clipboard = inject(Clipboard); + private readonly notificationService = inject(NotificationService); + private readonly snackBar = inject(MatSnackBar); + + public constructor() { addIcons({ copyOutline, createOutline, @@ -72,27 +82,19 @@ export class GfAccessTableComponent implements OnChanges { lockOpenOutline, removeCircleOutline }); - } - public ngOnChanges() { - this.displayedColumns = ['alias', 'grantee', 'type', 'details']; - - if (this.showActions) { - this.displayedColumns.push('actions'); - } - - if (this.accesses) { - this.dataSource = new MatTableDataSource(this.accesses); - } + effect(() => { + this.dataSource.data = this.accesses() ?? []; + }); } - public getPublicUrl(aId: string): string { - const languageCode = this.user.settings.language; + protected getPublicUrl(aId: string) { + const languageCode = this.user().settings.language; return `${this.baseUrl}/${languageCode}/${publicRoutes.public.path}/${aId}`; } - public onCopyUrlToClipboard(aId: string): void { + protected onCopyUrlToClipboard(aId: string) { this.clipboard.copy(this.getPublicUrl(aId)); this.snackBar.open( @@ -104,7 +106,7 @@ export class GfAccessTableComponent implements OnChanges { ); } - public onDeleteAccess(aId: string) { + protected onDeleteAccess(aId: string) { this.notificationService.confirm({ confirmFn: () => { this.accessDeleted.emit(aId); @@ -114,7 +116,7 @@ export class GfAccessTableComponent implements OnChanges { }); } - public onUpdateAccess(aId: string) { + protected onUpdateAccess(aId: string) { this.accessToUpdate.emit(aId); } } diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts index 380fb69cb..7a3040391 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts @@ -26,11 +26,12 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, Inject, - OnDestroy, OnInit } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog'; @@ -49,8 +50,7 @@ import { } from 'ionicons/icons'; import { isNumber } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { forkJoin, Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { forkJoin } from 'rxjs'; import { AccountDetailDialogParams } from './interfaces/interfaces'; @@ -77,7 +77,7 @@ import { AccountDetailDialogParams } from './interfaces/interfaces'; styleUrls: ['./account-detail-dialog.component.scss'], templateUrl: 'account-detail-dialog.html' }) -export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { +export class GfAccountDetailDialogComponent implements OnInit { public accountBalances: AccountBalancesResponse['balances']; public activities: OrderWithAccount[]; public activitiesCount: number; @@ -104,18 +104,17 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { public user: User; public valueInBaseCurrency: number; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: AccountDetailDialogParams, private dataService: DataService, + private destroyRef: DestroyRef, public dialogRef: MatDialogRef, private router: Router, private userService: UserService ) { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -154,7 +153,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { public onAddAccountBalance(accountBalance: CreateAccountBalanceDto) { this.dataService .postAccountBalance(accountBalance) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.initialize(); }); @@ -163,7 +162,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { public onDeleteAccountBalance(aId: string) { this.dataService .deleteAccountBalance(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.initialize(); }); @@ -176,7 +175,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { this.dataService .fetchExport({ activityIds }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { downloadAsFile({ content: data, @@ -212,7 +211,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { private fetchAccount() { this.dataService .fetchAccount(this.data.accountId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe( ({ activitiesCount, @@ -287,7 +286,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { sortColumn: this.sortColumn, sortDirection: this.sortDirection }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ activities, count }) => { this.dataSource = new MatTableDataSource(activities); this.totalItems = count; @@ -304,7 +303,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { forkJoin({ accountBalances: this.dataService .fetchAccountBalances(this.data.accountId) - .pipe(takeUntil(this.unsubscribeSubject)), + .pipe(takeUntilDestroyed(this.destroyRef)), portfolioPerformance: this.dataService .fetchPortfolioPerformance({ filters: [ @@ -317,7 +316,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { withExcludedAccounts: true, withItems: true }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) }).subscribe({ error: () => { this.isLoadingChart = false; @@ -360,7 +359,7 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { } ] }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ holdings }) => { this.holdings = holdings; @@ -374,9 +373,4 @@ export class GfAccountDetailDialogComponent implements OnDestroy, OnInit { this.fetchChart(); this.fetchPortfolioHoldings(); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html index 15dd8f13a..54e786734 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html @@ -24,7 +24,7 @@ class="h-100" [currency]="user?.settings?.baseCurrency" [historicalDataItems]="historicalDataItems" - [isInPercent]=" + [isInPercentage]=" data.hasImpersonationId || user.settings.isRestrictedView " [isLoading]="isLoadingChart" @@ -102,8 +102,6 @@
Holdings
(); public defaultDateTimeFormat: string; public filterForm: FormGroup; + public displayedColumns = [ 'index', 'type', @@ -93,21 +98,24 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { 'status', 'actions' ]; + + public hasPermissionToAccessBullBoard = false; public isLoading = false; public statusFilterOptions = QUEUE_JOB_STATUS_LIST; - public user: User; - private unsubscribeSubject = new Subject(); + private user: User; public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private formBuilder: FormBuilder, private notificationService: NotificationService, + private tokenStorageService: TokenStorageService, private userService: UserService ) { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -115,6 +123,11 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { this.defaultDateTimeFormat = getDateWithTimeFormatString( this.user.settings.locale ); + + this.hasPermissionToAccessBullBoard = hasPermission( + this.user.permissions, + permissions.accessAdminControlBullBoard + ); } }); @@ -126,6 +139,7 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { chevronUpCircleOutline, ellipsisHorizontal, ellipsisVertical, + openOutline, pauseOutline, playOutline, removeCircleOutline, @@ -139,7 +153,7 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { }); this.filterForm.valueChanges - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { const currentFilter = this.filterForm.get('status').value; this.fetchJobs(currentFilter ? [currentFilter] : undefined); @@ -151,7 +165,7 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { public onDeleteJob(aId: string) { this.adminService .deleteJob(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.fetchJobs(); }); @@ -162,7 +176,7 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { this.adminService .deleteJobs({ status: currentFilter ? [currentFilter] : undefined }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.fetchJobs(currentFilter ? [currentFilter] : undefined); }); @@ -171,12 +185,24 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { public onExecuteJob(aId: string) { this.adminService .executeJob(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.fetchJobs(); }); } + public onOpenBullBoard() { + const token = this.tokenStorageService.getToken(); + + document.cookie = [ + `${BULL_BOARD_COOKIE_NAME}=${encodeURIComponent(token)}`, + 'path=/', + 'SameSite=Strict' + ].join('; '); + + window.open(BULL_BOARD_ROUTE, '_blank'); + } + public onViewData(aData: AdminJobs['jobs'][0]['data']) { this.notificationService.alert({ title: JSON.stringify(aData, null, ' ') @@ -189,17 +215,12 @@ export class GfAdminJobsComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private fetchJobs(aStatus?: JobStatus[]) { this.isLoading = true; this.adminService .fetchJobs({ status: aStatus }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ jobs }) => { this.dataSource = new MatTableDataSource(jobs); this.dataSource.sort = this.sort; diff --git a/apps/client/src/app/components/admin-jobs/admin-jobs.html b/apps/client/src/app/components/admin-jobs/admin-jobs.html index a82294001..cff80498c 100644 --- a/apps/client/src/app/components/admin-jobs/admin-jobs.html +++ b/apps/client/src/app/components/admin-jobs/admin-jobs.html @@ -1,6 +1,15 @@
+ @if (hasPermissionToAccessBullBoard) { +
+ +
+ } +
diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index 8f956b782..d3265946f 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -26,10 +26,11 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatDialog } from '@angular/material/dialog'; @@ -63,7 +64,7 @@ import { import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject } from 'rxjs'; -import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; +import { distinctUntilChanged } from 'rxjs/operators'; import { AdminMarketDataService } from './admin-market-data.service'; import { GfAssetProfileDialogComponent } from './asset-profile-dialog/asset-profile-dialog.component'; @@ -95,9 +96,7 @@ import { CreateAssetProfileDialogParams } from './create-asset-profile-dialog/in styleUrls: ['./admin-market-data.scss'], templateUrl: './admin-market-data.html' }) -export class GfAdminMarketDataComponent - implements AfterViewInit, OnDestroy, OnInit -{ +export class GfAdminMarketDataComponent implements AfterViewInit, OnInit { @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @@ -140,6 +139,11 @@ export class GfAdminMarketDataComponent id: 'ETF_WITHOUT_SECTORS', label: $localize`ETFs without Sectors`, type: 'PRESET_ID' as Filter['type'] + }, + { + id: 'NO_ACTIVITIES', + label: $localize`No Activities`, + type: 'PRESET_ID' as Filter['type'] } ]; public benchmarks: Partial[]; @@ -161,13 +165,12 @@ export class GfAdminMarketDataComponent public totalItems = 0; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( public adminMarketDataService: AdminMarketDataService, private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private route: ActivatedRoute, @@ -204,7 +207,7 @@ export class GfAdminMarketDataComponent this.displayedColumns.push('actions'); this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if ( params['assetProfileDialog'] && @@ -221,7 +224,7 @@ export class GfAdminMarketDataComponent }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -233,7 +236,7 @@ export class GfAdminMarketDataComponent }); this.filters$ - .pipe(distinctUntilChanged(), takeUntil(this.unsubscribeSubject)) + .pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)) .subscribe((filters) => { this.activeFilters = filters; @@ -297,7 +300,7 @@ export class GfAdminMarketDataComponent public onGather7Days() { this.adminService .gather7Days() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { setTimeout(() => { window.location.reload(); @@ -308,7 +311,7 @@ export class GfAdminMarketDataComponent public onGatherMax() { this.adminService .gatherMax() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { setTimeout(() => { window.location.reload(); @@ -319,7 +322,7 @@ export class GfAdminMarketDataComponent public onGatherProfileData() { this.adminService .gatherProfileData() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } @@ -329,14 +332,14 @@ export class GfAdminMarketDataComponent }: AssetProfileIdentifier) { this.adminService .gatherProfileDataBySymbol({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } public onGatherSymbol({ dataSource, symbol }: AssetProfileIdentifier) { this.adminService .gatherSymbol({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } @@ -353,11 +356,6 @@ export class GfAdminMarketDataComponent }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private loadData( { pageIndex, @@ -374,7 +372,7 @@ export class GfAdminMarketDataComponent this.pageSize = this.activeFilters.length === 1 && this.activeFilters[0].type === 'PRESET_ID' - ? undefined + ? Number.MAX_SAFE_INTEGER : DEFAULT_PAGE_SIZE; if (pageIndex === 0 && this.paginator) { @@ -394,7 +392,7 @@ export class GfAdminMarketDataComponent skip: pageIndex * this.pageSize, take: this.pageSize }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ count, marketData }) => { this.totalItems = count; @@ -425,7 +423,7 @@ export class GfAdminMarketDataComponent }) { this.userService .get() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -447,7 +445,7 @@ export class GfAdminMarketDataComponent dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe( (newAssetProfileIdentifier: AssetProfileIdentifier | undefined) => { if (newAssetProfileIdentifier) { @@ -463,7 +461,7 @@ export class GfAdminMarketDataComponent private openCreateAssetProfileDialog() { this.userService .get() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -481,7 +479,7 @@ export class GfAdminMarketDataComponent dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((result) => { if (!result) { this.router.navigate(['.'], { relativeTo: this.route }); @@ -494,7 +492,7 @@ export class GfAdminMarketDataComponent if (addAssetProfile && dataSource && symbol) { this.adminService .addAssetProfile({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.loadData(); }); diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index 604ed1a67..6e2dab956 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/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, @@ -33,12 +37,13 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, ElementRef, Inject, - OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { AbstractControl, FormBuilder, @@ -83,8 +88,8 @@ import { serverOutline } from 'ionicons/icons'; import ms from 'ms'; -import { EMPTY, Subject } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; +import { catchError } from 'rxjs/operators'; import { AssetProfileDialogParams } from './interfaces/interfaces'; @@ -117,7 +122,7 @@ import { AssetProfileDialogParams } from './interfaces/interfaces'; styleUrls: ['./asset-profile-dialog.component.scss'], templateUrl: 'asset-profile-dialog.html' }) -export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { +export class GfAssetProfileDialogComponent implements OnInit { private static readonly HISTORICAL_DATA_TEMPLATE = `date;marketPrice\n${format( new Date(), DATE_FORMAT @@ -138,7 +143,6 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { }); public assetSubClassOptions: AssetClassSelectorOption[] = []; - public assetProfile: AdminMarketDataDetails['assetProfile']; public assetProfileForm = this.formBuilder.group({ @@ -180,12 +184,14 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { ); public benchmarks: Partial[]; + public canEditAssetProfile = true; public countries: { [code: string]: { name: string; value: number }; }; public currencies: string[] = []; + public dateRangeOptions = [ { label: $localize`Current week` + ' (' + $localize`WTD` + ')', @@ -236,14 +242,13 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { public user: User; - private unsubscribeSubject = new Subject(); - public constructor( public adminMarketDataService: AdminMarketDataService, private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogParams, private dataService: DataService, + private destroyRef: DestroyRef, public dialogRef: MatDialogRef, private formBuilder: FormBuilder, private notificationService: NotificationService, @@ -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() { @@ -277,7 +282,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { this.adminService .fetchAdminData() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ settings }) => { this.isDataGatheringEnabled = settings[PROPERTY_IS_DATA_GATHERING_ENABLED] === false ? false : true; @@ -286,7 +291,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -295,7 +300,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { this.assetProfileForm .get('assetClass') - .valueChanges.pipe(takeUntil(this.unsubscribeSubject)) + .valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((assetClass) => { const assetSubClasses = ASSET_CLASS_MAPPING.get(assetClass) ?? []; @@ -318,12 +323,17 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { dataSource: this.data.dataSource, symbol: this.data.symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ assetProfile, marketData }) => { this.assetProfile = assetProfile; 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(); } @@ -420,7 +436,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { }: AssetProfileIdentifier) { this.adminService .gatherProfileDataBySymbol({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } @@ -433,7 +449,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { } & AssetProfileIdentifier) { this.adminService .gatherSymbol({ dataSource, range, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } @@ -446,7 +462,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { public onSetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { this.dataService .postBenchmark({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.dataService.updateInfo(); @@ -648,7 +664,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { const newAssetProfileIdentifier = { @@ -698,7 +714,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { }); return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(({ price }) => { this.notificationService.alert({ @@ -729,7 +745,7 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { public onUnsetBenchmark({ dataSource, symbol }: AssetProfileIdentifier) { this.dataService .deleteBenchmark({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.dataService.updateInfo(); @@ -739,11 +755,6 @@ export class GfAssetProfileDialogComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - public onTriggerSubmitAssetProfileForm() { if (this.assetProfileForm.valid) { this.onSubmitAssetProfileForm(); diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index 2c248c626..9ae7f8064 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -280,7 +280,7 @@ @@ -290,7 +290,7 @@ @@ -300,6 +300,7 @@
Data Gathering 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 fbf8afa03..35887abb4 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 @@ -10,9 +10,10 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { AbstractControl, FormBuilder, @@ -31,7 +32,7 @@ import { MatInputModule } from '@angular/material/input'; import { MatRadioModule } from '@angular/material/radio'; import { DataSource } from '@prisma/client'; import { isISO4217CurrencyCode } from 'class-validator'; -import { Subject, switchMap, takeUntil } from 'rxjs'; +import { switchMap } from 'rxjs'; import { CreateAssetProfileDialogMode } from './interfaces/interfaces'; @@ -52,19 +53,19 @@ import { CreateAssetProfileDialogMode } from './interfaces/interfaces'; styleUrls: ['./create-asset-profile-dialog.component.scss'], templateUrl: 'create-asset-profile-dialog.html' }) -export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { +export class GfCreateAssetProfileDialogComponent implements OnInit { public createAssetProfileForm: FormGroup; public ghostfolioPrefix = `${ghostfolioPrefix}_`; public mode: CreateAssetProfileDialogMode; private customCurrencies: string[]; private dataSourceForExchangeRates: DataSource; - private unsubscribeSubject = new Subject(); public constructor( public readonly adminService: AdminService, private readonly changeDetectorRef: ChangeDetectorRef, private readonly dataService: DataService, + private readonly destroyRef: DestroyRef, public readonly dialogRef: MatDialogRef, public readonly formBuilder: FormBuilder ) {} @@ -125,7 +126,7 @@ export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { symbol: `${DEFAULT_CURRENCY}${currency}` }); }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { this.dialogRef.close({ @@ -154,11 +155,6 @@ export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { return false; } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private atLeastOneValid(control: AbstractControl): ValidationErrors { const addCurrencyControl = control.get('addCurrency'); const addSymbolControl = control.get('addSymbol'); @@ -189,7 +185,7 @@ export class GfCreateAssetProfileDialogComponent implements OnDestroy, OnInit { private initialize() { this.adminService .fetchAdminData() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ dataProviders, settings }) => { this.customCurrencies = settings[PROPERTY_CURRENCIES] as string[]; 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 c0ccb0f64..5d4e5268e 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 @@ -22,7 +22,13 @@ import { AdminService, DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule } from '@angular/common'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + DestroyRef, + OnInit +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; @@ -50,8 +56,6 @@ import { trashOutline } from 'ionicons/icons'; import ms, { StringValue } from 'ms'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [ @@ -72,7 +76,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./admin-overview.scss'], templateUrl: './admin-overview.html' }) -export class GfAdminOverviewComponent implements OnDestroy, OnInit { +export class GfAdminOverviewComponent implements OnInit { public activitiesCount: number; public couponDuration: StringValue = '14 days'; public coupons: Coupon[]; @@ -88,13 +92,12 @@ export class GfAdminOverviewComponent implements OnDestroy, OnInit { public user: User; public version: string; - private unsubscribeSubject = new Subject(); - public constructor( private adminService: AdminService, private cacheService: CacheService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private notificationService: NotificationService, private snackBar: MatSnackBar, private userService: UserService @@ -102,7 +105,7 @@ export class GfAdminOverviewComponent implements OnDestroy, OnInit { this.info = this.dataService.fetchInfo(); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -219,7 +222,7 @@ export class GfAdminOverviewComponent implements OnDestroy, OnInit { confirmFn: () => { this.cacheService .flush() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { setTimeout(() => { window.location.reload(); @@ -268,7 +271,7 @@ export class GfAdminOverviewComponent implements OnDestroy, OnInit { public onSyncDemoUserAccount() { this.adminService .syncDemoUserAccount() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.snackBar.open( '✅ ' + $localize`Demo user account has been synced.`, @@ -280,15 +283,10 @@ export class GfAdminOverviewComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private fetchAdminData() { this.adminService .fetchAdminData() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ activitiesCount, settings, userCount, version }) => { this.activitiesCount = activitiesCount; this.coupons = (settings[PROPERTY_COUPONS] as Coupon[]) ?? []; @@ -320,7 +318,7 @@ export class GfAdminOverviewComponent implements OnDestroy, OnInit { .putAdminSetting(key, { value: value || value === false ? JSON.stringify(value) : undefined }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { setTimeout(() => { window.location.reload(); 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 f0a6ea1d5..55fa8dac5 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.html +++ b/apps/client/src/app/components/admin-overview/admin-overview.html @@ -6,7 +6,10 @@
Version
- +
diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.html b/apps/client/src/app/components/admin-platform/admin-platform.component.html index 367827878..7370a19ae 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.html +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -52,7 +52,11 @@ Accounts - {{ element.accountCount }} + diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.ts b/apps/client/src/app/components/admin-platform/admin-platform.component.ts index 02a2eed64..b8f2af789 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.ts +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.ts @@ -1,18 +1,22 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; +import { getLocale } from '@ghostfolio/common/helper'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { AdminService, DataService } from '@ghostfolio/ui/services'; +import { GfValueComponent } from '@ghostfolio/ui/value'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, + Input, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; @@ -29,7 +33,6 @@ import { } from 'ionicons/icons'; import { get } from 'lodash'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject, takeUntil } from 'rxjs'; import { GfCreateOrUpdatePlatformDialogComponent } from './create-or-update-platform-dialog/create-or-update-platform-dialog.component'; import { CreateOrUpdatePlatformDialogParams } from './create-or-update-platform-dialog/interfaces/interfaces'; @@ -38,6 +41,7 @@ import { CreateOrUpdatePlatformDialogParams } from './create-or-update-platform- changeDetection: ChangeDetectionStrategy.OnPush, imports: [ GfEntityLogoComponent, + GfValueComponent, IonIcon, MatButtonModule, MatMenuModule, @@ -49,7 +53,9 @@ import { CreateOrUpdatePlatformDialogParams } from './create-or-update-platform- styleUrls: ['./admin-platform.component.scss'], templateUrl: './admin-platform.component.html' }) -export class GfAdminPlatformComponent implements OnDestroy, OnInit { +export class GfAdminPlatformComponent implements OnInit { + @Input() locale = getLocale(); + @ViewChild(MatSort) sort: MatSort; public dataSource = new MatTableDataSource(); @@ -57,12 +63,11 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { public displayedColumns = ['name', 'url', 'accounts', 'actions']; public platforms: Platform[]; - private unsubscribeSubject = new Subject(); - public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private notificationService: NotificationService, @@ -71,7 +76,7 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { private userService: UserService ) { this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if (params['createPlatformDialog']) { this.openCreatePlatformDialog(); @@ -113,20 +118,15 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private deletePlatform(aId: string) { this.adminService .deletePlatform(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchPlatforms(); @@ -137,7 +137,7 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { private fetchPlatforms() { this.adminService .fetchPlatforms() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((platforms) => { this.platforms = platforms; @@ -169,17 +169,17 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((platform: CreatePlatformDto | null) => { if (platform) { this.adminService .postPlatform(platform) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchPlatforms(); @@ -217,17 +217,17 @@ export class GfAdminPlatformComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((platform: UpdatePlatformDto | null) => { if (platform) { this.adminService .putPlatform(platform) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchPlatforms(); diff --git a/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts b/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts index dfcf300c1..0cfe026a4 100644 --- a/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts +++ b/apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts @@ -2,12 +2,7 @@ import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos'; import { validateObjectForForm } from '@ghostfolio/common/utils'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; -import { - ChangeDetectionStrategy, - Component, - Inject, - OnDestroy -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { FormBuilder, FormGroup, @@ -23,7 +18,6 @@ import { } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; -import { Subject } from 'rxjs'; import { CreateOrUpdatePlatformDialogParams } from './interfaces/interfaces'; @@ -43,11 +37,9 @@ import { CreateOrUpdatePlatformDialogParams } from './interfaces/interfaces'; styleUrls: ['./create-or-update-platform-dialog.scss'], templateUrl: 'create-or-update-platform-dialog.html' }) -export class GfCreateOrUpdatePlatformDialogComponent implements OnDestroy { +export class GfCreateOrUpdatePlatformDialogComponent { public platformForm: FormGroup; - private unsubscribeSubject = new Subject(); - public constructor( @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdatePlatformDialogParams, public dialogRef: MatDialogRef, @@ -90,9 +82,4 @@ export class GfCreateOrUpdatePlatformDialogComponent implements OnDestroy { console.error(error); } } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.html b/apps/client/src/app/components/admin-settings/admin-settings.component.html index af64f034b..76af96c4e 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.html +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.html @@ -40,9 +40,21 @@ } - +
- diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.ts b/apps/client/src/app/components/admin-tag/admin-tag.component.ts index ca7950291..506736156 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.ts +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.ts @@ -1,17 +1,21 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; +import { getLocale } from '@ghostfolio/common/helper'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { DataService } from '@ghostfolio/ui/services'; +import { GfValueComponent } from '@ghostfolio/ui/value'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, + Input, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; @@ -28,7 +32,6 @@ import { } from 'ionicons/icons'; import { get } from 'lodash'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject, takeUntil } from 'rxjs'; import { GfCreateOrUpdateTagDialogComponent } from './create-or-update-tag-dialog/create-or-update-tag-dialog.component'; import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/interfaces/interfaces'; @@ -36,6 +39,7 @@ import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/int @Component({ changeDetection: ChangeDetectionStrategy.OnPush, imports: [ + GfValueComponent, IonIcon, MatButtonModule, MatMenuModule, @@ -47,7 +51,9 @@ import { CreateOrUpdateTagDialogParams } from './create-or-update-tag-dialog/int styleUrls: ['./admin-tag.component.scss'], templateUrl: './admin-tag.component.html' }) -export class GfAdminTagComponent implements OnDestroy, OnInit { +export class GfAdminTagComponent implements OnInit { + @Input() locale = getLocale(); + @ViewChild(MatSort) sort: MatSort; public dataSource = new MatTableDataSource(); @@ -55,11 +61,10 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { public displayedColumns = ['name', 'userId', 'activities', 'actions']; public tags: Tag[]; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private notificationService: NotificationService, @@ -68,7 +73,7 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { private userService: UserService ) { this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if (params['createTagDialog']) { this.openCreateTagDialog(); @@ -110,20 +115,15 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private deleteTag(aId: string) { this.dataService .deleteTag(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchTags(); @@ -134,7 +134,7 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { private fetchTags() { this.dataService .fetchTags() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((tags) => { this.tags = tags; @@ -165,17 +165,17 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((tag: CreateTagDto | null) => { if (tag) { this.dataService .postTag(tag) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchTags(); @@ -204,17 +204,17 @@ export class GfAdminTagComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((tag: UpdateTagDto | null) => { if (tag) { this.dataService .putTag(tag) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchTags(); diff --git a/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts b/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts index 323609a48..e22c73478 100644 --- a/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts +++ b/apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts @@ -1,12 +1,7 @@ import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos'; import { validateObjectForForm } from '@ghostfolio/common/utils'; -import { - ChangeDetectionStrategy, - Component, - Inject, - OnDestroy -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { FormBuilder, FormGroup, @@ -21,7 +16,6 @@ import { } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; -import { Subject } from 'rxjs'; import { CreateOrUpdateTagDialogParams } from './interfaces/interfaces'; @@ -40,11 +34,9 @@ import { CreateOrUpdateTagDialogParams } from './interfaces/interfaces'; styleUrls: ['./create-or-update-tag-dialog.scss'], templateUrl: 'create-or-update-tag-dialog.html' }) -export class GfCreateOrUpdateTagDialogComponent implements OnDestroy { +export class GfCreateOrUpdateTagDialogComponent { public tagForm: FormGroup; - private unsubscribeSubject = new Subject(); - public constructor( @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateTagDialogParams, public dialogRef: MatDialogRef, @@ -85,9 +77,4 @@ export class GfCreateOrUpdateTagDialogComponent implements OnDestroy { console.error(error); } } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index d479f2037..874bbc1db 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -1,7 +1,6 @@ import { UserDetailDialogParams } from '@ghostfolio/client/components/user-detail-dialog/interfaces/interfaces'; import { GfUserDetailDialogComponent } from '@ghostfolio/client/components/user-detail-dialog/user-detail-dialog.component'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; -import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; @@ -26,10 +25,11 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; @@ -56,8 +56,7 @@ import { } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject } from 'rxjs'; -import { switchMap, takeUntil, tap } from 'rxjs/operators'; +import { switchMap, tap } from 'rxjs/operators'; @Component({ imports: [ @@ -76,7 +75,7 @@ import { switchMap, takeUntil, tap } from 'rxjs/operators'; styleUrls: ['./admin-users.scss'], templateUrl: './admin-users.html' }) -export class GfAdminUsersComponent implements OnDestroy, OnInit { +export class GfAdminUsersComponent implements OnInit { @ViewChild(MatPaginator) paginator: MatPaginator; public dataSource = new MatTableDataSource(); @@ -94,19 +93,17 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { public totalItems = 0; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private impersonationStorageService: ImpersonationStorageService, private notificationService: NotificationService, private route: ActivatedRoute, private router: Router, - private tokenStorageService: TokenStorageService, private userService: UserService ) { this.deviceType = this.deviceService.getDeviceInfo().deviceType; @@ -141,7 +138,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { this.userService.stateChanged .pipe( - takeUntil(this.unsubscribeSubject), + takeUntilDestroyed(this.destroyRef), tap((state) => { if (state?.user) { this.user = state.user; @@ -206,7 +203,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { confirmFn: () => { this.dataService .deleteUser(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.router.navigate(['..'], { relativeTo: this.route }); }); @@ -224,13 +221,12 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { confirmFn: () => { this.dataService .updateUserAccessToken(aUserId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ accessToken }) => { this.notificationService.alert({ discardFn: () => { if (aUserId === this.user.id) { - this.tokenStorageService.signOut(); - this.userService.remove(); + this.userService.signOut(); document.location.href = `/${document.documentElement.lang}`; } @@ -261,11 +257,6 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { ); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private fetchUsers({ pageIndex }: { pageIndex: number } = { pageIndex: 0 }) { this.isLoading = true; @@ -278,7 +269,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { skip: pageIndex * this.pageSize, take: this.pageSize }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ count, users }) => { this.dataSource = new MatTableDataSource(users); this.totalItems = count; @@ -308,7 +299,7 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { if (data?.action === 'delete' && data?.userId) { this.onDeleteUser(data.userId); diff --git a/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts b/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts index e44e81be9..a77d65961 100644 --- a/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts +++ b/apps/client/src/app/components/data-provider-status/data-provider-status.component.ts @@ -4,13 +4,14 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, + DestroyRef, Input, - OnDestroy, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import type { DataSource } from '@prisma/client'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { catchError, map, type Observable, of, Subject, takeUntil } from 'rxjs'; +import { catchError, map, type Observable, of } from 'rxjs'; import { DataProviderStatus } from './interfaces/interfaces'; @@ -20,14 +21,15 @@ import { DataProviderStatus } from './interfaces/interfaces'; selector: 'gf-data-provider-status', templateUrl: './data-provider-status.component.html' }) -export class GfDataProviderStatusComponent implements OnDestroy, OnInit { +export class GfDataProviderStatusComponent implements OnInit { @Input() dataSource: DataSource; public status$: Observable; - private unsubscribeSubject = new Subject(); - - public constructor(private dataService: DataService) {} + public constructor( + private dataService: DataService, + private destroyRef: DestroyRef + ) {} public ngOnInit() { this.status$ = this.dataService @@ -39,12 +41,7 @@ export class GfDataProviderStatusComponent implements OnDestroy, OnInit { catchError(() => { return of({ isHealthy: false }); }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index 9b003a590..ab329251f 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -24,6 +24,7 @@ import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, + DestroyRef, EventEmitter, HostListener, Input, @@ -31,6 +32,7 @@ import { Output, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatBadgeModule } from '@angular/material/badge'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; @@ -48,8 +50,8 @@ import { radioButtonOffOutline, radioButtonOnOutline } from 'ionicons/icons'; -import { EMPTY, Subject } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; +import { catchError } from 'rxjs/operators'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -131,10 +133,9 @@ export class GfHeaderComponent implements OnChanges { public routerLinkRegister = publicRoutes.register.routerLink; public routerLinkResources = publicRoutes.resources.routerLink; - private unsubscribeSubject = new Subject(); - public constructor( private dataService: DataService, + private destroyRef: DestroyRef, private dialog: MatDialog, private impersonationStorageService: ImpersonationStorageService, private layoutService: LayoutService, @@ -146,7 +147,7 @@ export class GfHeaderComponent implements OnChanges { ) { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; this.impersonationId = impersonationId; @@ -224,11 +225,11 @@ export class GfHeaderComponent implements OnChanges { public onDateRangeChange(dateRange: DateRange) { this.dataService .putUserSetting({ dateRange }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); }); } @@ -252,11 +253,11 @@ export class GfHeaderComponent implements OnChanges { this.dataService .putUserSetting(userSetting) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); }); } @@ -301,7 +302,7 @@ export class GfHeaderComponent implements OnChanges { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { if (data?.accessToken) { this.dataService @@ -314,7 +315,7 @@ export class GfHeaderComponent implements OnChanges { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(({ authToken }) => { this.setToken(authToken); @@ -331,7 +332,7 @@ export class GfHeaderComponent implements OnChanges { this.userService .get() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { const userLanguage = user?.settings?.language; @@ -342,9 +343,4 @@ export class GfHeaderComponent implements OnChanges { } }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } 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 427386796..13ded73eb 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 @@ -35,10 +35,11 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, Inject, - OnDestroy, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatChipsModule } from '@angular/material/chips'; @@ -67,8 +68,7 @@ import { walletOutline } from 'ionicons/icons'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject } from 'rxjs'; -import { switchMap, takeUntil } from 'rxjs/operators'; +import { switchMap } from 'rxjs/operators'; import { HoldingDetailDialogParams } from './interfaces/interfaces'; @@ -102,7 +102,7 @@ import { HoldingDetailDialogParams } from './interfaces/interfaces'; styleUrls: ['./holding-detail-dialog.component.scss'], templateUrl: 'holding-detail-dialog.html' }) -export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { +export class GfHoldingDetailDialogComponent implements OnInit { public activitiesCount: number; public accounts: Account[]; public assetClass: string; @@ -158,11 +158,10 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { public user: User; public value: number; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: HoldingDetailDialogParams, private formBuilder: FormBuilder, @@ -192,7 +191,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { this.holdingForm .get('tags') - .valueChanges.pipe(takeUntil(this.unsubscribeSubject)) + .valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((tags: Tag[]) => { const newTag = tags.find(({ id }) => { return id === undefined; @@ -217,7 +216,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { switchMap(() => { return this.userService.get(true); }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(); } else { @@ -227,7 +226,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { dataSource: this.data.dataSource, symbol: this.data.symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); } }); @@ -236,7 +235,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { .fetchAccounts({ filters }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ accounts }) => { this.accounts = accounts; @@ -249,7 +248,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { sortColumn: this.sortColumn, sortDirection: this.sortDirection }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ activities }) => { this.dataSource = new MatTableDataSource(activities); @@ -261,7 +260,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { dataSource: this.data.dataSource, symbol: this.data.symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe( ({ activitiesCount, @@ -524,7 +523,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { ); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -581,8 +580,8 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { }; this.dataService - .postOrder(activity) - .pipe(takeUntil(this.unsubscribeSubject)) + .postActivity(activity) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.router.navigate( internalRoutes.portfolio.subRoutes.activities.routerLink @@ -599,7 +598,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { this.dataService .fetchExport({ activityIds }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { downloadAsFile({ content: data, @@ -629,18 +628,13 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { this.dialogRef.close(); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private fetchMarketData() { this.dataService .fetchMarketDataBySymbol({ dataSource: this.data.dataSource, symbol: this.data.symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ marketData }) => { this.marketDataItems = marketData; diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html index 86f4676f3..11771dee2 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -286,7 +286,7 @@ [baseCurrency]="data.baseCurrency" [colorScheme]="data.colorScheme" [data]="sectors" - [isInPercent]="true" + [isInPercentage]="true" [keys]="['name']" [locale]="data.locale" [maxItems]="10" @@ -298,7 +298,7 @@ [baseCurrency]="data.baseCurrency" [colorScheme]="data.colorScheme" [data]="countries" - [isInPercent]="true" + [isInPercentage]="true" [keys]="['name']" [locale]="data.locale" [maxItems]="10" @@ -310,6 +310,9 @@ Symbol ISIN diff --git a/apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts index aab854384..527b13636 100644 --- a/apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts @@ -11,7 +11,7 @@ export interface HoldingDetailDialogParams { hasPermissionToAccessAdminControl: boolean; hasPermissionToCreateActivity: boolean; hasPermissionToReportDataGlitch: boolean; - hasPermissionToUpdateOrder: boolean; + hasPermissionToUpdateActivity: boolean; locale: string; symbol: string; } diff --git a/apps/client/src/app/components/home-holdings/home-holdings.component.ts b/apps/client/src/app/components/home-holdings/home-holdings.component.ts index dc444977d..19a48ccd8 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.component.ts +++ b/apps/client/src/app/components/home-holdings/home-holdings.component.ts @@ -19,9 +19,10 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; @@ -30,8 +31,6 @@ import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { gridOutline, reorderFourOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [ @@ -51,7 +50,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./home-holdings.scss'], templateUrl: './home-holdings.html' }) -export class GfHomeHoldingsComponent implements OnDestroy, OnInit { +export class GfHomeHoldingsComponent implements OnInit { public static DEFAULT_HOLDINGS_VIEW_MODE: HoldingsViewMode = 'TABLE'; public deviceType: string; @@ -71,11 +70,10 @@ export class GfHomeHoldingsComponent implements OnDestroy, OnInit { GfHomeHoldingsComponent.DEFAULT_HOLDINGS_VIEW_MODE ); - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private impersonationStorageService: ImpersonationStorageService, private router: Router, @@ -89,13 +87,13 @@ export class GfHomeHoldingsComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -107,7 +105,7 @@ export class GfHomeHoldingsComponent implements OnDestroy, OnInit { this.hasPermissionToCreateActivity = hasPermission( this.user.permissions, - permissions.createOrder + permissions.createActivity ); this.initialize(); @@ -117,15 +115,15 @@ export class GfHomeHoldingsComponent implements OnDestroy, OnInit { }); this.viewModeFormControl.valueChanges - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((holdingsViewMode) => { this.dataService .putUserSetting({ holdingsViewMode }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -149,11 +147,6 @@ export class GfHomeHoldingsComponent implements OnDestroy, OnInit { } } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private fetchHoldings() { const filters = this.userService.getFilters(); @@ -193,7 +186,7 @@ export class GfHomeHoldingsComponent implements OnDestroy, OnInit { this.holdings = undefined; this.fetchHoldings() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ holdings }) => { this.holdings = holdings; diff --git a/apps/client/src/app/components/home-holdings/home-holdings.html b/apps/client/src/app/components/home-holdings/home-holdings.html index 8b4e4c5d3..175c88606 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.html +++ b/apps/client/src/app/components/home-holdings/home-holdings.html @@ -46,9 +46,6 @@ }
(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private userService: UserService ) { @@ -59,7 +57,7 @@ export class GfHomeMarketComponent implements OnDestroy, OnInit { this.info = this.dataService.fetchInfo(); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -82,7 +80,7 @@ export class GfHomeMarketComponent implements OnDestroy, OnInit { includeHistoricalData: this.numberOfDays, symbol: ghostfolioFearAndGreedIndexSymbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ historicalData, marketPrice }) => { this.fearAndGreedIndex = marketPrice; this.historicalDataItems = [ @@ -99,16 +97,11 @@ export class GfHomeMarketComponent implements OnDestroy, OnInit { this.dataService .fetchBenchmarks() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ benchmarks }) => { this.benchmarks = benchmarks; this.changeDetectorRef.markForCheck(); }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/home-overview/home-overview.component.ts b/apps/client/src/app/components/home-overview/home-overview.component.ts index 0d5020904..58284d27d 100644 --- a/apps/client/src/app/components/home-overview/home-overview.component.ts +++ b/apps/client/src/app/components/home-overview/home-overview.component.ts @@ -19,14 +19,13 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [ @@ -41,7 +40,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./home-overview.scss'], templateUrl: './home-overview.html' }) -export class GfHomeOverviewComponent implements OnDestroy, OnInit { +export class GfHomeOverviewComponent implements OnInit { public deviceType: string; public errors: AssetProfileIdentifier[]; public hasError: boolean; @@ -62,25 +61,24 @@ export class GfHomeOverviewComponent implements OnDestroy, OnInit { public unit: string; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private impersonationStorageService: ImpersonationStorageService, private layoutService: LayoutService, private userService: UserService ) { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; this.hasPermissionToCreateActivity = hasPermission( this.user.permissions, - permissions.createOrder + permissions.createActivity ); this.update(); @@ -99,7 +97,7 @@ export class GfHomeOverviewComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; @@ -107,17 +105,12 @@ export class GfHomeOverviewComponent implements OnDestroy, OnInit { }); this.layoutService.shouldReloadContent$ - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.update(); }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private update() { this.historicalDataItems = null; this.isLoadingPerformance = true; @@ -126,7 +119,7 @@ export class GfHomeOverviewComponent implements OnDestroy, OnInit { .fetchPortfolioPerformance({ range: this.user?.settings?.dateRange }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ chart, errors, performance }) => { this.errors = errors; this.performance = performance; diff --git a/apps/client/src/app/components/home-summary/home-summary.component.ts b/apps/client/src/app/components/home-summary/home-summary.component.ts index 454d05689..719cfbd29 100644 --- a/apps/client/src/app/components/home-summary/home-summary.component.ts +++ b/apps/client/src/app/components/home-summary/home-summary.component.ts @@ -13,14 +13,13 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatCardModule } from '@angular/material/card'; import { MatSnackBarRef, TextOnlySnackBar } from '@angular/material/snack-bar'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [GfPortfolioSummaryComponent, MatCardModule], @@ -29,7 +28,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./home-summary.scss'], templateUrl: './home-summary.html' }) -export class GfHomeSummaryComponent implements OnDestroy, OnInit { +export class GfHomeSummaryComponent implements OnInit { public deviceType: string; public hasImpersonationId: boolean; public hasPermissionForSubscription: boolean; @@ -40,11 +39,10 @@ export class GfHomeSummaryComponent implements OnDestroy, OnInit { public summary: PortfolioSummary; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private impersonationStorageService: ImpersonationStorageService, private userService: UserService @@ -57,7 +55,7 @@ export class GfHomeSummaryComponent implements OnDestroy, OnInit { ); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -77,7 +75,7 @@ export class GfHomeSummaryComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); @@ -86,11 +84,11 @@ export class GfHomeSummaryComponent implements OnDestroy, OnInit { public onChangeEmergencyFund(emergencyFund: number) { this.dataService .putUserSetting({ emergencyFund }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -99,17 +97,12 @@ export class GfHomeSummaryComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private update() { this.isLoading = true; this.dataService .fetchPortfolioDetails() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ summary }) => { this.summary = summary; this.isLoading = false; diff --git a/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts index 60d74be92..4669a827d 100644 --- a/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts +++ b/apps/client/src/app/components/home-watchlist/create-watchlist-item-dialog/create-watchlist-item-dialog.component.ts @@ -1,11 +1,6 @@ import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete'; -import { - ChangeDetectionStrategy, - Component, - OnDestroy, - OnInit -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { AbstractControl, FormBuilder, @@ -19,7 +14,6 @@ import { import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; -import { Subject } from 'rxjs'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -36,11 +30,9 @@ import { Subject } from 'rxjs'; styleUrls: ['./create-watchlist-item-dialog.component.scss'], templateUrl: 'create-watchlist-item-dialog.html' }) -export class GfCreateWatchlistItemDialogComponent implements OnDestroy, OnInit { +export class GfCreateWatchlistItemDialogComponent implements OnInit { public createWatchlistItemForm: FormGroup; - private unsubscribeSubject = new Subject(); - public constructor( public readonly dialogRef: MatDialogRef, public readonly formBuilder: FormBuilder @@ -69,11 +61,6 @@ export class GfCreateWatchlistItemDialogComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private validator(control: AbstractControl): ValidationErrors { const searchSymbolControl = control.get('searchSymbol'); diff --git a/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts b/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts index 4adb4e54f..e6f366351 100644 --- a/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts +++ b/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts @@ -15,9 +15,10 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; @@ -25,8 +26,6 @@ import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { addOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; import { GfCreateWatchlistItemDialogComponent } from './create-watchlist-item-dialog/create-watchlist-item-dialog.component'; import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/interfaces/interfaces'; @@ -45,7 +44,7 @@ import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/ styleUrls: ['./home-watchlist.scss'], templateUrl: './home-watchlist.html' }) -export class GfHomeWatchlistComponent implements OnDestroy, OnInit { +export class GfHomeWatchlistComponent implements OnInit { public deviceType: string; public hasImpersonationId: boolean; public hasPermissionToCreateWatchlistItem: boolean; @@ -53,11 +52,10 @@ export class GfHomeWatchlistComponent implements OnDestroy, OnInit { public user: User; public watchlist: Benchmark[]; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private impersonationStorageService: ImpersonationStorageService, @@ -69,13 +67,13 @@ export class GfHomeWatchlistComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if (params['createWatchlistItemDialog']) { this.openCreateWatchlistItemDialog(); @@ -83,7 +81,7 @@ export class GfHomeWatchlistComponent implements OnDestroy, OnInit { }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -118,7 +116,7 @@ export class GfHomeWatchlistComponent implements OnDestroy, OnInit { }: AssetProfileIdentifier) { this.dataService .deleteWatchlistItem({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { return this.loadWatchlistData(); @@ -126,15 +124,10 @@ export class GfHomeWatchlistComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private loadWatchlistData() { this.dataService .fetchWatchlist() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ watchlist }) => { this.watchlist = watchlist; @@ -145,7 +138,7 @@ export class GfHomeWatchlistComponent implements OnDestroy, OnInit { private openCreateWatchlistItemDialog() { this.userService .get() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -163,12 +156,12 @@ export class GfHomeWatchlistComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ dataSource, symbol } = {}) => { if (dataSource && symbol) { this.dataService .postWatchlistItem({ dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => this.loadWatchlistData() }); diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.ts b/apps/client/src/app/components/investment-chart/investment-chart.component.ts index 53d4f5693..21a7ac85a 100644 --- a/apps/client/src/app/components/investment-chart/investment-chart.component.ts +++ b/apps/client/src/app/components/investment-chart/investment-chart.component.ts @@ -61,7 +61,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { @Input() currency: string; @Input() groupBy: GroupBy; @Input() historicalDataItems: LineChartItem[] = []; - @Input() isInPercent = false; + @Input() isInPercentage = false; @Input() isLoading = false; @Input() locale = getLocale(); @Input() savingsRate = 0; @@ -119,7 +119,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { data: this.investments.map(({ date, investment }) => { return { x: parseDate(date).getTime(), - y: this.isInPercent ? investment * 100 : investment + y: this.isInPercentage ? investment * 100 : investment }; }), label: this.benchmarkDataLabel, @@ -139,7 +139,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { data: this.values.map(({ date, value }) => { return { x: parseDate(date).getTime(), - y: this.isInPercent ? value * 100 : value + y: this.isInPercentage ? value * 100 : value }; }), fill: false, @@ -251,7 +251,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { border: { display: false }, - display: !this.isInPercent, + display: !this.isInPercentage, grid: { color: ({ scale, tick }) => { if ( @@ -292,10 +292,10 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { return { ...getTooltipOptions({ colorScheme: this.colorScheme, - currency: this.isInPercent ? undefined : this.currency, + currency: this.isInPercentage ? undefined : this.currency, groupBy: this.groupBy, - locale: this.isInPercent ? undefined : this.locale, - unit: this.isInPercent ? '%' : undefined + locale: this.isInPercentage ? undefined : this.locale, + unit: this.isInPercentage ? '%' : undefined }), mode: 'index', position: 'top', diff --git a/apps/client/src/app/components/markets/markets.component.ts b/apps/client/src/app/components/markets/markets.component.ts index 4b83e897f..4214ee989 100644 --- a/apps/client/src/app/components/markets/markets.component.ts +++ b/apps/client/src/app/components/markets/markets.component.ts @@ -19,12 +19,11 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -39,7 +38,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./markets.scss'], templateUrl: './markets.html' }) -export class GfMarketsComponent implements OnDestroy, OnInit { +export class GfMarketsComponent implements OnInit { public benchmarks: Benchmark[]; public deviceType: string; public fearAndGreedIndex: number; @@ -55,18 +54,17 @@ export class GfMarketsComponent implements OnDestroy, OnInit { public readonly numberOfDays = 365; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private userService: UserService ) { this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -79,7 +77,7 @@ export class GfMarketsComponent implements OnDestroy, OnInit { public ngOnInit() { this.dataService .fetchMarketDataOfMarkets({ includeHistoricalData: this.numberOfDays }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ fearAndGreedIndex }) => { this.fearAndGreedIndexData = fearAndGreedIndex; @@ -90,7 +88,7 @@ export class GfMarketsComponent implements OnDestroy, OnInit { this.dataService .fetchBenchmarks() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ benchmarks }) => { this.benchmarks = benchmarks; @@ -119,9 +117,4 @@ export class GfMarketsComponent implements OnDestroy, OnInit { this.initialize(); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html index 46eb2845c..0e26a49a8 100644 --- a/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html +++ b/apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html @@ -65,7 +65,11 @@ [locale]="locale" [precision]="precision" [unit]="baseCurrency" - [value]="isLoading ? undefined : summary?.committedFunds" + [value]=" + isLoading + ? undefined + : summary?.totalInvestmentValueWithCurrencyEffect + " />
@@ -180,18 +184,21 @@ [ngClass]="{ 'cursor-pointer': hasPermissionToUpdateUserSettings && + !user?.settings?.isRestrictedView && user?.subscription?.type !== 'Basic' }" (click)=" hasPermissionToUpdateUserSettings && + !user?.settings?.isRestrictedView && user?.subscription?.type !== 'Basic' && onEditEmergencyFund() " > @if ( hasPermissionToUpdateUserSettings && - user?.subscription?.type !== 'Basic' && - !isLoading + !isLoading && + !user?.settings?.isRestrictedView && + user?.subscription?.type !== 'Basic' ) { (); private deviceType: string; - private unsubscribeSubject = new Subject(); - public constructor( + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog ) { @@ -94,7 +94,7 @@ export class GfRuleComponent implements OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((settings: RuleSettings) => { if (settings) { this.ruleUpdated.emit({ @@ -115,9 +115,4 @@ export class GfRuleComponent implements OnInit { this.ruleUpdated.emit(settings); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts b/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts index 0d616b92e..d10be10bd 100644 --- a/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts +++ b/apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts @@ -7,9 +7,11 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, + DestroyRef, Inject, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MAT_DIALOG_DATA, @@ -21,8 +23,8 @@ import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { arrowForwardOutline, checkmarkCircleOutline } from 'ionicons/icons'; import ms from 'ms'; -import { interval, Subject } from 'rxjs'; -import { take, takeUntil, tap } from 'rxjs/operators'; +import { interval } from 'rxjs'; +import { take, tap } from 'rxjs/operators'; import { SubscriptionInterstitialDialogParams } from './interfaces/interfaces'; @@ -51,11 +53,10 @@ export class GfSubscriptionInterstitialDialogComponent implements OnInit { public routerLinkPricing = publicRoutes.pricing.routerLink; public variantIndex: number; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: SubscriptionInterstitialDialogParams, + private destroyRef: DestroyRef, public dialogRef: MatDialogRef ) { this.variantIndex = Math.floor( @@ -76,7 +77,7 @@ export class GfSubscriptionInterstitialDialogComponent implements OnInit { this.changeDetectorRef.markForCheck(); }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(); } @@ -84,9 +85,4 @@ export class GfSubscriptionInterstitialDialogComponent implements OnInit { public closeDialog() { this.dialogRef.close({}); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts index 5c87b2f63..97bd272d4 100644 --- a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts +++ b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts @@ -3,14 +3,16 @@ import { validateObjectForForm } from '@ghostfolio/common/utils'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { DataService } from '@ghostfolio/ui/services'; +import type { HttpErrorResponse } from '@angular/common/http'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - Inject, - OnDestroy, + DestroyRef, + inject, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, FormGroup, @@ -28,7 +30,7 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { StatusCodes } from 'http-status-codes'; -import { EMPTY, Subject, catchError, takeUntil } from 'rxjs'; +import { EMPTY, catchError } from 'rxjs'; import { CreateOrUpdateAccessDialogParams } from './interfaces/interfaces'; @@ -48,22 +50,25 @@ import { CreateOrUpdateAccessDialogParams } from './interfaces/interfaces'; styleUrls: ['./create-or-update-access-dialog.scss'], templateUrl: 'create-or-update-access-dialog.html' }) -export class GfCreateOrUpdateAccessDialogComponent - implements OnDestroy, OnInit -{ - public accessForm: FormGroup; - public mode: 'create' | 'update'; - - private unsubscribeSubject = new Subject(); - - public constructor( - private changeDetectorRef: ChangeDetectorRef, - @Inject(MAT_DIALOG_DATA) private data: CreateOrUpdateAccessDialogParams, - public dialogRef: MatDialogRef, - private dataService: DataService, - private formBuilder: FormBuilder, - private notificationService: NotificationService - ) { +export class GfCreateOrUpdateAccessDialogComponent implements OnInit { + protected accessForm: FormGroup; + protected mode: 'create' | 'update'; + + private readonly changeDetectorRef = inject(ChangeDetectorRef); + + private readonly data = + inject(MAT_DIALOG_DATA); + + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + + private readonly dialogRef = + inject>(MatDialogRef); + + private readonly formBuilder = inject(FormBuilder); + private readonly notificationService = inject(NotificationService); + + public constructor() { this.mode = this.data.access?.id ? 'update' : 'create'; } @@ -83,22 +88,25 @@ export class GfCreateOrUpdateAccessDialogComponent ] }); - this.accessForm.get('type').valueChanges.subscribe((accessType) => { - const granteeUserIdControl = this.accessForm.get('granteeUserId'); - const permissionsControl = this.accessForm.get('permissions'); + this.accessForm + .get('type') + ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((accessType) => { + const granteeUserIdControl = this.accessForm.get('granteeUserId'); + const permissionsControl = this.accessForm.get('permissions'); - if (accessType === 'PRIVATE') { - granteeUserIdControl.setValidators(Validators.required); - } else { - granteeUserIdControl.clearValidators(); - granteeUserIdControl.setValue(null); - permissionsControl.setValue(this.data.access.permissions[0]); - } + if (accessType === 'PRIVATE') { + granteeUserIdControl?.setValidators(Validators.required); + } else { + granteeUserIdControl?.clearValidators(); + granteeUserIdControl?.setValue(null); + permissionsControl?.setValue(this.data.access.permissions[0]); + } - granteeUserIdControl.updateValueAndValidity(); + granteeUserIdControl?.updateValueAndValidity(); - this.changeDetectorRef.markForCheck(); - }); + this.changeDetectorRef.markForCheck(); + }); } public onCancel() { @@ -113,16 +121,11 @@ export class GfCreateOrUpdateAccessDialogComponent } } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private async createAccess() { const access: CreateAccessDto = { - alias: this.accessForm.get('alias').value, - granteeUserId: this.accessForm.get('granteeUserId').value, - permissions: [this.accessForm.get('permissions').value] + alias: this.accessForm.get('alias')?.value, + granteeUserId: this.accessForm.get('granteeUserId')?.value, + permissions: [this.accessForm.get('permissions')?.value] }; try { @@ -135,7 +138,7 @@ export class GfCreateOrUpdateAccessDialogComponent this.dataService .postAccess(access) .pipe( - catchError((error) => { + catchError((error: HttpErrorResponse) => { if (error.status === StatusCodes.BAD_REQUEST) { this.notificationService.alert({ title: $localize`Oops! Could not grant access.` @@ -144,7 +147,7 @@ export class GfCreateOrUpdateAccessDialogComponent return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { this.dialogRef.close(access); @@ -156,10 +159,10 @@ export class GfCreateOrUpdateAccessDialogComponent private async updateAccess() { const access: UpdateAccessDto = { - alias: this.accessForm.get('alias').value, - granteeUserId: this.accessForm.get('granteeUserId').value, + alias: this.accessForm.get('alias')?.value, + granteeUserId: this.accessForm.get('granteeUserId')?.value, id: this.data.access.id, - permissions: [this.accessForm.get('permissions').value] + permissions: [this.accessForm.get('permissions')?.value] }; try { @@ -172,8 +175,8 @@ export class GfCreateOrUpdateAccessDialogComponent this.dataService .putAccess(access) .pipe( - catchError(({ status }) => { - if (status.status === StatusCodes.BAD_REQUEST) { + catchError(({ status }: HttpErrorResponse) => { + if (status === StatusCodes.BAD_REQUEST) { this.notificationService.alert({ title: $localize`Oops! Could not update access.` }); @@ -181,7 +184,7 @@ export class GfCreateOrUpdateAccessDialogComponent return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { this.dialogRef.close(access); diff --git a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html index 11669041d..93614b55a 100644 --- a/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html +++ b/apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -38,13 +38,13 @@ Permission Restricted view - @if (accessForm.get('type').value === 'PRIVATE') { + @if (accessForm.get('type')?.value === 'PRIVATE') { View } - @if (accessForm.get('type').value === 'PRIVATE') { + @if (accessForm.get('type')?.value === 'PRIVATE') {
diff --git a/apps/client/src/app/components/user-account-access/user-account-access.component.ts b/apps/client/src/app/components/user-account-access/user-account-access.component.ts index ef78cccff..f8620c745 100644 --- a/apps/client/src/app/components/user-account-access/user-account-access.component.ts +++ b/apps/client/src/app/components/user-account-access/user-account-access.component.ts @@ -1,5 +1,4 @@ import { GfAccessTableComponent } from '@ghostfolio/client/components/access-table/access-table.component'; -import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { CreateAccessDto } from '@ghostfolio/common/dtos'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; @@ -14,9 +13,10 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; @@ -27,8 +27,8 @@ import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { addOutline, eyeOffOutline, eyeOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { EMPTY, Subject } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; +import { catchError } from 'rxjs/operators'; import { GfCreateOrUpdateAccessDialogComponent } from './create-or-update-access-dialog/create-or-update-access-dialog.component'; import { CreateOrUpdateAccessDialogParams } from './create-or-update-access-dialog/interfaces/interfaces'; @@ -52,7 +52,7 @@ import { CreateOrUpdateAccessDialogParams } from './create-or-update-access-dial styleUrls: ['./user-account-access.scss'], templateUrl: './user-account-access.html' }) -export class GfUserAccountAccessComponent implements OnDestroy, OnInit { +export class GfUserAccountAccessComponent implements OnInit { public accessesGet: Access[]; public accessesGive: Access[]; public deviceType: string; @@ -65,18 +65,16 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit { }); public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private formBuilder: FormBuilder, private notificationService: NotificationService, private route: ActivatedRoute, private router: Router, - private tokenStorageService: TokenStorageService, private userService: UserService ) { const { globalPermissions } = this.dataService.fetchInfo(); @@ -87,7 +85,7 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit { ); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -112,7 +110,7 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit { }); this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if (params['createDialog']) { this.openCreateAccessDialog(); @@ -133,7 +131,7 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit { public onDeleteAccess(aId: string) { this.dataService .deleteAccess(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.update(); @@ -156,13 +154,12 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(({ accessToken }) => { this.notificationService.alert({ discardFn: () => { - this.tokenStorageService.signOut(); - this.userService.remove(); + this.userService.signOut(); document.location.href = `/${document.documentElement.lang}`; }, @@ -182,11 +179,6 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private openCreateAccessDialog() { const dialogRef = this.dialog.open< GfCreateOrUpdateAccessDialogComponent, @@ -220,8 +212,6 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit { }); if (!access) { - console.log('Could not find access.'); - return; } @@ -264,7 +254,7 @@ export class GfUserAccountAccessComponent implements OnDestroy, OnInit { this.dataService .fetchAccesses() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((accesses) => { this.accessesGive = accesses; diff --git a/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts b/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts index 92fd0d590..b13a983fc 100644 --- a/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts +++ b/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts @@ -14,15 +14,16 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy + DestroyRef } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatSnackBar } from '@angular/material/snack-bar'; import { RouterModule } from '@angular/router'; import ms, { StringValue } from 'ms'; -import { EMPTY, Subject } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; +import { catchError } from 'rxjs/operators'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -38,7 +39,7 @@ import { catchError, takeUntil } from 'rxjs/operators'; styleUrls: ['./user-account-membership.scss'], templateUrl: './user-account-membership.html' }) -export class GfUserAccountMembershipComponent implements OnDestroy { +export class GfUserAccountMembershipComponent { public baseCurrency: string; public coupon: number; public couponId: string; @@ -54,11 +55,10 @@ export class GfUserAccountMembershipComponent implements OnDestroy { 'mailto:hi@ghostfol.io?Subject=Ghostfolio Premium Trial&body=Hello%0D%0DI am interested in Ghostfolio Premium. Can you please send me a coupon code to try it for some time?%0D%0DKind regards'; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private notificationService: NotificationService, private snackBar: MatSnackBar, private userService: UserService @@ -73,7 +73,7 @@ export class GfUserAccountMembershipComponent implements OnDestroy { ); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -118,7 +118,7 @@ export class GfUserAccountMembershipComponent implements OnDestroy { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(({ sessionUrl }) => { window.location.href = sessionUrl; @@ -142,7 +142,7 @@ export class GfUserAccountMembershipComponent implements OnDestroy { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(({ apiKey }) => { this.notificationService.alert({ @@ -180,7 +180,7 @@ export class GfUserAccountMembershipComponent implements OnDestroy { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { const snackBarRef = this.snackBar.open( @@ -193,14 +193,14 @@ export class GfUserAccountMembershipComponent implements OnDestroy { snackBarRef .afterDismissed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { window.location.reload(); }); snackBarRef .onAction() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { window.location.reload(); }); @@ -210,9 +210,4 @@ export class GfUserAccountMembershipComponent implements OnDestroy { title: $localize`Please enter your coupon code.` }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts b/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts index 44be30b9a..72bbfc2c6 100644 --- a/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts +++ b/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -3,7 +3,6 @@ import { KEY_TOKEN, SettingsStorageService } from '@ghostfolio/client/services/settings-storage.service'; -import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; @@ -19,9 +18,10 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, FormsModule, @@ -44,8 +44,8 @@ import { format, parseISO } from 'date-fns'; import { addIcons } from 'ionicons'; import { eyeOffOutline, eyeOutline } from 'ionicons/icons'; import ms from 'ms'; -import { EMPTY, Subject, throwError } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { EMPTY, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -66,7 +66,7 @@ import { catchError, takeUntil } from 'rxjs/operators'; styleUrls: ['./user-account-settings.scss'], templateUrl: './user-account-settings.html' }) -export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { +export class GfUserAccountSettingsComponent implements OnInit { public appearancePlaceholder = $localize`Auto`; public baseCurrency: string; public currencies: string[] = []; @@ -99,16 +99,14 @@ export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { ]; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private formBuilder: FormBuilder, private notificationService: NotificationService, private settingsStorageService: SettingsStorageService, private snackBar: MatSnackBar, - private tokenStorageService: TokenStorageService, private userService: UserService, public webAuthnService: WebAuthnService ) { @@ -118,7 +116,7 @@ export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { this.currencies = currencies; this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -159,11 +157,11 @@ export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { public onChangeUserSetting(aKey: string, aValue: string) { this.dataService .putUserSetting({ [aKey]: aValue }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -195,11 +193,10 @@ export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { - this.tokenStorageService.signOut(); - this.userService.remove(); + this.userService.signOut(); document.location.href = `/${document.documentElement.lang}`; }); @@ -212,11 +209,11 @@ export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { public onExperimentalFeaturesChange(aEvent: MatSlideToggleChange) { this.dataService .putUserSetting({ isExperimentalFeatures: aEvent.checked }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -228,7 +225,7 @@ export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { public onExport() { this.dataService .fetchExport() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { for (const activity of data.activities) { delete activity.id; @@ -248,11 +245,11 @@ export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { public onRestrictedViewChange(aEvent: MatSlideToggleChange) { this.dataService .putUserSetting({ isRestrictedView: aEvent.checked }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -287,11 +284,11 @@ export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { public onViewModeChange(aEvent: MatSlideToggleChange) { this.dataService .putUserSetting({ viewMode: aEvent.checked === true ? 'ZEN' : 'DEFAULT' }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -300,11 +297,6 @@ export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private deregisterDevice() { this.webAuthnService .deregister() @@ -314,7 +306,7 @@ export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { this.update(); @@ -344,7 +336,7 @@ export class GfUserAccountSettingsComponent implements OnDestroy, OnInit { return error; }); }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe({ next: () => { diff --git a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts index 6f7f4ead6..fec2d4b06 100644 --- a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts +++ b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.component.ts @@ -7,10 +7,11 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, + DestroyRef, Inject, - OnDestroy, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog'; @@ -18,8 +19,8 @@ import { MatMenuModule } from '@angular/material/menu'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { ellipsisVertical } from 'ionicons/icons'; -import { EMPTY, Subject } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; +import { catchError } from 'rxjs/operators'; import { UserDetailDialogParams } from './interfaces/interfaces'; @@ -38,15 +39,14 @@ import { UserDetailDialogParams } from './interfaces/interfaces'; styleUrls: ['./user-detail-dialog.component.scss'], templateUrl: './user-detail-dialog.html' }) -export class GfUserDetailDialogComponent implements OnDestroy, OnInit { +export class GfUserDetailDialogComponent implements OnInit { public user: AdminUserResponse; - private unsubscribeSubject = new Subject(); - public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: UserDetailDialogParams, + private destroyRef: DestroyRef, public dialogRef: MatDialogRef ) { addIcons({ @@ -58,7 +58,7 @@ export class GfUserDetailDialogComponent implements OnDestroy, OnInit { this.adminService .fetchUserById(this.data.userId) .pipe( - takeUntil(this.unsubscribeSubject), + takeUntilDestroyed(this.destroyRef), catchError(() => { this.dialogRef.close(); @@ -82,9 +82,4 @@ export class GfUserDetailDialogComponent implements OnDestroy, OnInit { public onClose() { this.dialogRef.close(); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html index 570dcf4d6..e9af86942 100644 --- a/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html +++ b/apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html @@ -27,7 +27,13 @@
- User ID + User ID
Role diff --git a/apps/client/src/app/core/auth.guard.ts b/apps/client/src/app/core/auth.guard.ts index 123a6169a..3292f0ff7 100644 --- a/apps/client/src/app/core/auth.guard.ts +++ b/apps/client/src/app/core/auth.guard.ts @@ -68,7 +68,7 @@ export class AuthGuard { this.dataService .putUserSetting({ language: document.documentElement.lang }) .subscribe(() => { - this.userService.remove(); + this.userService.reset(); setTimeout(() => { window.location.reload(); diff --git a/apps/client/src/app/core/http-response.interceptor.ts b/apps/client/src/app/core/http-response.interceptor.ts index ab99b440f..315e9d64e 100644 --- a/apps/client/src/app/core/http-response.interceptor.ts +++ b/apps/client/src/app/core/http-response.interceptor.ts @@ -1,4 +1,4 @@ -import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; +import { UserService } from '@ghostfolio/client/services/user/user.service'; import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; import { InfoItem } from '@ghostfolio/common/interfaces'; import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes'; @@ -32,8 +32,8 @@ export class HttpResponseInterceptor implements HttpInterceptor { public constructor( private dataService: DataService, private router: Router, - private tokenStorageService: TokenStorageService, private snackBar: MatSnackBar, + private userService: UserService, private webAuthnService: WebAuthnService ) { this.info = this.dataService.fetchInfo(); @@ -115,7 +115,7 @@ export class HttpResponseInterceptor implements HttpInterceptor { if (this.webAuthnService.isEnabled()) { this.router.navigate(internalRoutes.webauthn.routerLink); } else { - this.tokenStorageService.signOut(); + this.userService.signOut(); } } } diff --git a/apps/client/src/app/interfaces/interfaces.ts b/apps/client/src/app/interfaces/interfaces.ts new file mode 100644 index 000000000..493eff4ab --- /dev/null +++ b/apps/client/src/app/interfaces/interfaces.ts @@ -0,0 +1,8 @@ +import type { Params } from '@angular/router'; +import type { DataSource } from '@prisma/client'; + +export interface GfAppQueryParams extends Params { + dataSource?: DataSource; + holdingDetailDialog?: string; + symbol?: string; +} diff --git a/apps/client/src/app/pages/about/about-page.component.ts b/apps/client/src/app/pages/about/about-page.component.ts index 5ddb6b2e0..1e749d1cd 100644 --- a/apps/client/src/app/pages/about/about-page.component.ts +++ b/apps/client/src/app/pages/about/about-page.component.ts @@ -8,9 +8,10 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatTabsModule } from '@angular/material/tabs'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; @@ -24,8 +25,6 @@ import { sparklesOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ host: { class: 'page has-tabs' }, @@ -35,17 +34,16 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./about-page.scss'], templateUrl: './about-page.html' }) -export class AboutPageComponent implements OnDestroy, OnInit { +export class AboutPageComponent implements OnInit { public deviceType: string; public hasPermissionForSubscription: boolean; public tabs: TabConfiguration[] = []; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private userService: UserService ) { @@ -57,7 +55,7 @@ export class AboutPageComponent implements OnDestroy, OnInit { ); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { this.tabs = [ { @@ -118,9 +116,4 @@ export class AboutPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/about/changelog/changelog-page.component.ts b/apps/client/src/app/pages/about/changelog/changelog-page.component.ts index 69b397370..d7f583bd1 100644 --- a/apps/client/src/app/pages/about/changelog/changelog-page.component.ts +++ b/apps/client/src/app/pages/about/changelog/changelog-page.component.ts @@ -1,7 +1,6 @@ -import { Component, OnDestroy } from '@angular/core'; +import { Component } from '@angular/core'; import { MarkdownModule } from 'ngx-markdown'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject } from 'rxjs'; @Component({ imports: [MarkdownModule, NgxSkeletonLoaderModule], @@ -9,17 +8,10 @@ import { Subject } from 'rxjs'; styleUrls: ['./changelog-page.scss'], templateUrl: './changelog-page.html' }) -export class GfChangelogPageComponent implements OnDestroy { +export class GfChangelogPageComponent { public isLoading = true; - private unsubscribeSubject = new Subject(); - public onLoad() { this.isLoading = false; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/about/license/license-page.component.ts b/apps/client/src/app/pages/about/license/license-page.component.ts index 0dc5b2f51..d530d0418 100644 --- a/apps/client/src/app/pages/about/license/license-page.component.ts +++ b/apps/client/src/app/pages/about/license/license-page.component.ts @@ -1,6 +1,5 @@ -import { Component, OnDestroy } from '@angular/core'; +import { Component } from '@angular/core'; import { MarkdownModule } from 'ngx-markdown'; -import { Subject } from 'rxjs'; @Component({ imports: [MarkdownModule], @@ -8,11 +7,4 @@ import { Subject } from 'rxjs'; styleUrls: ['./license-page.scss'], templateUrl: './license-page.html' }) -export class GfLicensePageComponent implements OnDestroy { - private unsubscribeSubject = new Subject(); - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } -} +export class GfLicensePageComponent {} diff --git a/apps/client/src/app/pages/about/oss-friends/oss-friends-page.component.ts b/apps/client/src/app/pages/about/oss-friends/oss-friends-page.component.ts index bdbbdf9a7..c2e500a52 100644 --- a/apps/client/src/app/pages/about/oss-friends/oss-friends-page.component.ts +++ b/apps/client/src/app/pages/about/oss-friends/oss-friends-page.component.ts @@ -1,10 +1,9 @@ -import { Component, OnDestroy } from '@angular/core'; +import { Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { arrowForwardOutline } from 'ionicons/icons'; -import { Subject } from 'rxjs'; const ossFriends = require('../../../../assets/oss-friends.json'); @@ -14,17 +13,10 @@ const ossFriends = require('../../../../assets/oss-friends.json'); styleUrls: ['./oss-friends-page.scss'], templateUrl: './oss-friends-page.html' }) -export class GfOpenSourceSoftwareFriendsPageComponent implements OnDestroy { +export class GfOpenSourceSoftwareFriendsPageComponent { public ossFriends = ossFriends.data; - private unsubscribeSubject = new Subject(); - public constructor() { addIcons({ arrowForwardOutline }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/about/overview/about-overview-page.component.ts b/apps/client/src/app/pages/about/overview/about-overview-page.component.ts index bea19a1b9..9c399762b 100644 --- a/apps/client/src/app/pages/about/overview/about-overview-page.component.ts +++ b/apps/client/src/app/pages/about/overview/about-overview-page.component.ts @@ -9,9 +9,10 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; @@ -23,8 +24,6 @@ import { logoX, mail } from 'ionicons/icons'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [CommonModule, IonIcon, MatButtonModule, RouterModule], @@ -33,7 +32,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./about-overview-page.scss'], templateUrl: './about-overview-page.html' }) -export class GfAboutOverviewPageComponent implements OnDestroy, OnInit { +export class GfAboutOverviewPageComponent implements OnInit { public hasPermissionForStatistics: boolean; public hasPermissionForSubscription: boolean; public isLoggedIn: boolean; @@ -43,11 +42,10 @@ export class GfAboutOverviewPageComponent implements OnDestroy, OnInit { public routerLinkOpenStartup = publicRoutes.openStartup.routerLink; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private userService: UserService ) { const { globalPermissions } = this.dataService.fetchInfo(); @@ -67,7 +65,7 @@ export class GfAboutOverviewPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -76,9 +74,4 @@ export class GfAboutOverviewPageComponent implements OnDestroy, OnInit { } }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.component.ts b/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.component.ts index 78881cd2c..50e4e3e2f 100644 --- a/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.component.ts +++ b/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.component.ts @@ -1,6 +1,5 @@ -import { Component, OnDestroy } from '@angular/core'; +import { Component } from '@angular/core'; import { MarkdownModule } from 'ngx-markdown'; -import { Subject } from 'rxjs'; @Component({ imports: [MarkdownModule], @@ -8,11 +7,4 @@ import { Subject } from 'rxjs'; styleUrls: ['./privacy-policy-page.scss'], templateUrl: './privacy-policy-page.html' }) -export class GfPrivacyPolicyPageComponent implements OnDestroy { - private unsubscribeSubject = new Subject(); - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } -} +export class GfPrivacyPolicyPageComponent {} diff --git a/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts index dbf07ef19..7899f0187 100644 --- a/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts +++ b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts @@ -1,6 +1,5 @@ -import { Component, OnDestroy } from '@angular/core'; +import { Component } from '@angular/core'; import { MarkdownModule } from 'ngx-markdown'; -import { Subject } from 'rxjs'; @Component({ imports: [MarkdownModule], @@ -8,11 +7,4 @@ import { Subject } from 'rxjs'; styleUrls: ['./terms-of-service-page.scss'], templateUrl: './terms-of-service-page.html' }) -export class GfTermsOfServicePageComponent implements OnDestroy { - private unsubscribeSubject = new Subject(); - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } -} +export class GfTermsOfServicePageComponent {} diff --git a/apps/client/src/app/pages/accounts/accounts-page.component.ts b/apps/client/src/app/pages/accounts/accounts-page.component.ts index f7e6541b5..fdc78a8c4 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -13,7 +13,13 @@ import { GfAccountsTableComponent } from '@ghostfolio/ui/accounts-table'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { DataService } from '@ghostfolio/ui/services'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + DestroyRef, + OnInit +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; @@ -21,8 +27,8 @@ import { Account as AccountModel } from '@prisma/client'; import { addIcons } from 'ionicons'; import { addOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { EMPTY, Subject, Subscription } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { EMPTY, Subscription } from 'rxjs'; +import { catchError } from 'rxjs/operators'; import { GfCreateOrUpdateAccountDialogComponent } from './create-or-update-account-dialog/create-or-update-account-dialog.component'; import { CreateOrUpdateAccountDialogParams } from './create-or-update-account-dialog/interfaces/interfaces'; @@ -36,7 +42,7 @@ import { GfTransferBalanceDialogComponent } from './transfer-balance/transfer-ba styleUrls: ['./accounts-page.scss'], templateUrl: './accounts-page.html' }) -export class GfAccountsPageComponent implements OnDestroy, OnInit { +export class GfAccountsPageComponent implements OnInit { public accounts: AccountModel[]; public activitiesCount = 0; public deviceType: string; @@ -48,11 +54,10 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { public totalValueInBaseCurrency = 0; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private impersonationStorageService: ImpersonationStorageService, @@ -62,7 +67,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { private userService: UserService ) { this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if (params['accountId'] && params['accountDetailDialog']) { this.openAccountDetailDialog(params['accountId']); @@ -94,13 +99,13 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -124,7 +129,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { public fetchAccounts() { this.dataService .fetchAccounts() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe( ({ accounts, @@ -151,11 +156,11 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { this.dataService .deleteAccount(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchAccounts(); @@ -204,18 +209,18 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((account: UpdateAccountDto | null) => { if (account) { this.reset(); this.dataService .putAccount(account) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchAccounts(); @@ -228,11 +233,6 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private openAccountDetailDialog(aAccountId: string) { const dialogRef = this.dialog.open< GfAccountDetailDialogComponent, @@ -245,7 +245,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { hasImpersonationId: this.hasImpersonationId, hasPermissionToCreateActivity: !this.hasImpersonationId && - hasPermission(this.user?.permissions, permissions.createOrder) && + hasPermission(this.user?.permissions, permissions.createActivity) && !this.user?.settings?.isRestrictedView }, height: this.deviceType === 'mobile' ? '98vh' : '80vh', @@ -254,7 +254,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.fetchAccounts(); @@ -284,18 +284,18 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((account: CreateAccountDto | null) => { if (account) { this.reset(); this.dataService .postAccount(account) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchAccounts(); @@ -321,7 +321,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data: any) => { if (data) { this.reset(); @@ -343,7 +343,7 @@ export class GfAccountsPageComponent implements OnDestroy, OnInit { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { this.fetchAccounts(); diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts index f4c68e70f..53d8380e1 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts @@ -5,12 +5,7 @@ import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; import { DataService } from '@ghostfolio/ui/services'; import { CommonModule, NgClass } from '@angular/common'; -import { - ChangeDetectionStrategy, - Component, - Inject, - OnDestroy -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { AbstractControl, FormBuilder, @@ -30,7 +25,7 @@ import { import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { Platform } from '@prisma/client'; -import { Observable, Subject } from 'rxjs'; +import { Observable } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces'; @@ -55,20 +50,18 @@ import { CreateOrUpdateAccountDialogParams } from './interfaces/interfaces'; styleUrls: ['./create-or-update-account-dialog.scss'], templateUrl: 'create-or-update-account-dialog.html' }) -export class GfCreateOrUpdateAccountDialogComponent implements OnDestroy { - public accountForm: FormGroup; - public currencies: string[] = []; - public filteredPlatforms: Observable; - public platforms: Platform[] = []; - - private unsubscribeSubject = new Subject(); - - public constructor( - @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateAccountDialogParams, - private dataService: DataService, - public dialogRef: MatDialogRef, - private formBuilder: FormBuilder - ) {} +export class GfCreateOrUpdateAccountDialogComponent { + protected accountForm: FormGroup; + protected currencies: string[] = []; + protected filteredPlatforms: Observable | undefined; + protected platforms: Platform[] = []; + + protected readonly data = + inject(MAT_DIALOG_DATA); + private readonly dataService = inject(DataService); + private readonly dialogRef = + inject>(MatDialogRef); + private readonly formBuilder = inject(FormBuilder); public ngOnInit() { const { currencies } = this.dataService.fetchInfo(); @@ -100,18 +93,18 @@ export class GfCreateOrUpdateAccountDialogComponent implements OnDestroy { this.filteredPlatforms = this.accountForm .get('platformId') - .valueChanges.pipe( + ?.valueChanges.pipe( startWith(''), - map((value) => { + map((value: Platform | string) => { const name = typeof value === 'string' ? value : value?.name; - return name ? this.filter(name as string) : this.platforms.slice(); + return name ? this.filter(name) : this.platforms.slice(); }) ); }); } - public autoCompleteCheck() { - const inputValue = this.accountForm.get('platformId').value; + protected autoCompleteCheck() { + const inputValue = this.accountForm.get('platformId')?.value; if (typeof inputValue === 'string') { const matchingEntry = this.platforms.find(({ name }) => { @@ -119,28 +112,28 @@ export class GfCreateOrUpdateAccountDialogComponent implements OnDestroy { }); if (matchingEntry) { - this.accountForm.get('platformId').setValue(matchingEntry); + this.accountForm.get('platformId')?.setValue(matchingEntry); } } } - public displayFn(platform: Platform) { + protected displayFn(platform: Platform) { return platform?.name ?? ''; } - public onCancel() { + protected onCancel() { this.dialogRef.close(); } - public async onSubmit() { + protected async onSubmit() { const account: CreateAccountDto | UpdateAccountDto = { - balance: this.accountForm.get('balance').value, - comment: this.accountForm.get('comment').value || null, - currency: this.accountForm.get('currency').value, - id: this.accountForm.get('accountId').value, - isExcluded: this.accountForm.get('isExcluded').value, - name: this.accountForm.get('name').value, - platformId: this.accountForm.get('platformId').value?.id || null + balance: this.accountForm.get('balance')?.value, + comment: this.accountForm.get('comment')?.value || null, + currency: this.accountForm.get('currency')?.value, + id: this.accountForm.get('accountId')?.value, + isExcluded: this.accountForm.get('isExcluded')?.value, + name: this.accountForm.get('name')?.value, + platformId: this.accountForm.get('platformId')?.value?.id || null }; try { @@ -170,11 +163,6 @@ export class GfCreateOrUpdateAccountDialogComponent implements OnDestroy { } } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private autocompleteObjectValidator(): ValidatorFn { return (control: AbstractControl) => { if (control.value && typeof control.value === 'string') { @@ -189,7 +177,7 @@ export class GfCreateOrUpdateAccountDialogComponent implements OnDestroy { const filterValue = value.toLowerCase(); return this.platforms.filter(({ name }) => { - return name.toLowerCase().startsWith(filterValue); + return name?.toLowerCase().startsWith(filterValue); }); } } diff --git a/apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.component.ts b/apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.component.ts index 85d2e60bf..34a66b156 100644 --- a/apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.component.ts +++ b/apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.component.ts @@ -1,12 +1,7 @@ import { TransferBalanceDto } from '@ghostfolio/common/dtos'; import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo'; -import { - ChangeDetectionStrategy, - Component, - Inject, - OnDestroy -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { AbstractControl, FormBuilder, @@ -25,7 +20,6 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { Account } from '@prisma/client'; -import { Subject } from 'rxjs'; import { TransferBalanceDialogParams } from './interfaces/interfaces'; @@ -45,13 +39,11 @@ import { TransferBalanceDialogParams } from './interfaces/interfaces'; styleUrls: ['./transfer-balance-dialog.scss'], templateUrl: 'transfer-balance-dialog.html' }) -export class GfTransferBalanceDialogComponent implements OnDestroy { +export class GfTransferBalanceDialogComponent { public accounts: Account[] = []; public currency: string; public transferBalanceForm: FormGroup; - private unsubscribeSubject = new Subject(); - public constructor( @Inject(MAT_DIALOG_DATA) public data: TransferBalanceDialogParams, public dialogRef: MatDialogRef, @@ -93,11 +85,6 @@ export class GfTransferBalanceDialogComponent implements OnDestroy { this.dialogRef.close({ account }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private compareAccounts(control: AbstractControl): ValidationErrors { const accountFrom = control.get('fromAccount'); const accountTo = control.get('toAccount'); diff --git a/apps/client/src/app/pages/admin/admin-page.component.ts b/apps/client/src/app/pages/admin/admin-page.component.ts index b9243dcb9..66312ef33 100644 --- a/apps/client/src/app/pages/admin/admin-page.component.ts +++ b/apps/client/src/app/pages/admin/admin-page.component.ts @@ -1,7 +1,7 @@ import { TabConfiguration } from '@ghostfolio/common/interfaces'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { MatTabsModule } from '@angular/material/tabs'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; @@ -14,7 +14,6 @@ import { settingsOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page has-tabs' }, @@ -23,12 +22,10 @@ import { Subject } from 'rxjs'; styleUrls: ['./admin-page.scss'], templateUrl: './admin-page.html' }) -export class AdminPageComponent implements OnDestroy, OnInit { +export class AdminPageComponent implements OnInit { public deviceType: string; public tabs: TabConfiguration[] = []; - private unsubscribeSubject = new Subject(); - public constructor(private deviceService: DeviceDetectorService) { addIcons({ flashOutline, @@ -50,11 +47,7 @@ export class AdminPageComponent implements OnDestroy, OnInit { }, { iconName: 'settings-outline', - label: - internalRoutes.adminControl.subRoutes.settings.title + - '' + - $localize`new` + - '', + label: internalRoutes.adminControl.subRoutes.settings.title, routerLink: internalRoutes.adminControl.subRoutes.settings.routerLink }, { @@ -74,9 +67,4 @@ export class AdminPageComponent implements OnDestroy, OnInit { } ]; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/api/api-page.component.ts b/apps/client/src/app/pages/api/api-page.component.ts index 353605380..357a08bbd 100644 --- a/apps/client/src/app/pages/api/api-page.component.ts +++ b/apps/client/src/app/pages/api/api-page.component.ts @@ -14,9 +14,10 @@ import { import { CommonModule } from '@angular/common'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; -import { Component, OnInit } from '@angular/core'; +import { Component, DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { format, startOfYear } from 'date-fns'; -import { map, Observable, Subject, takeUntil } from 'rxjs'; +import { map, Observable } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -35,9 +36,11 @@ export class GfApiPageComponent implements OnInit { public status$: Observable; private apiKey: string; - private unsubscribeSubject = new Subject(); - public constructor(private http: HttpClient) {} + public constructor( + private destroyRef: DestroyRef, + private http: HttpClient + ) {} public ngOnInit() { this.apiKey = prompt($localize`Please enter your Ghostfolio API key:`); @@ -51,18 +54,13 @@ export class GfApiPageComponent implements OnInit { this.status$ = this.fetchStatus(); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private fetchAssetProfile({ symbol }: { symbol: string }) { return this.http .get( `/api/v1/data-providers/ghostfolio/asset-profile/${symbol}`, { headers: this.getHeaders() } ) - .pipe(takeUntil(this.unsubscribeSubject)); + .pipe(takeUntilDestroyed(this.destroyRef)); } private fetchDividends({ symbol }: { symbol: string }) { @@ -82,7 +80,7 @@ export class GfApiPageComponent implements OnInit { map(({ dividends }) => { return dividends; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ); } @@ -103,7 +101,7 @@ export class GfApiPageComponent implements OnInit { map(({ historicalData }) => { return historicalData; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ); } @@ -129,7 +127,7 @@ export class GfApiPageComponent implements OnInit { map(({ items }) => { return items; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ); } @@ -145,7 +143,7 @@ export class GfApiPageComponent implements OnInit { map(({ quotes }) => { return quotes; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ); } @@ -155,7 +153,7 @@ export class GfApiPageComponent implements OnInit { '/api/v2/data-providers/ghostfolio/status', { headers: this.getHeaders() } ) - .pipe(takeUntil(this.unsubscribeSubject)); + .pipe(takeUntilDestroyed(this.destroyRef)); } private getHeaders() { diff --git a/apps/client/src/app/pages/auth/auth-page.component.ts b/apps/client/src/app/pages/auth/auth-page.component.ts index 082401d6d..1b0b4a67c 100644 --- a/apps/client/src/app/pages/auth/auth-page.component.ts +++ b/apps/client/src/app/pages/auth/auth-page.component.ts @@ -4,20 +4,18 @@ import { } from '@ghostfolio/client/services/settings-storage.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ActivatedRoute, Router } from '@angular/router'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'gf-auth-page', styleUrls: ['./auth-page.scss'], templateUrl: './auth-page.html' }) -export class GfAuthPageComponent implements OnDestroy, OnInit { - private unsubscribeSubject = new Subject(); - +export class GfAuthPageComponent implements OnInit { public constructor( + private destroyRef: DestroyRef, private route: ActivatedRoute, private router: Router, private settingsStorageService: SettingsStorageService, @@ -26,7 +24,7 @@ export class GfAuthPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.route.params - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { const jwt = params['jwt']; @@ -38,9 +36,4 @@ export class GfAuthPageComponent implements OnDestroy, OnInit { this.router.navigate(['/']); }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/blog/blog-page.component.ts b/apps/client/src/app/pages/blog/blog-page.component.ts index 8a379a7e4..7f2c56d2d 100644 --- a/apps/client/src/app/pages/blog/blog-page.component.ts +++ b/apps/client/src/app/pages/blog/blog-page.component.ts @@ -1,13 +1,12 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { DataService } from '@ghostfolio/ui/services'; -import { Component, CUSTOM_ELEMENTS_SCHEMA, OnDestroy } from '@angular/core'; +import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { chevronForwardOutline } from 'ionicons/icons'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -17,11 +16,9 @@ import { Subject } from 'rxjs'; styleUrls: ['./blog-page.scss'], templateUrl: './blog-page.html' }) -export class GfBlogPageComponent implements OnDestroy { +export class GfBlogPageComponent { public hasPermissionForSubscription: boolean; - private unsubscribeSubject = new Subject(); - public constructor(private dataService: DataService) { const info = this.dataService.fetchInfo(); @@ -32,9 +29,4 @@ export class GfBlogPageComponent implements OnDestroy { addIcons({ chevronForwardOutline }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/demo/demo-page.component.ts b/apps/client/src/app/pages/demo/demo-page.component.ts index 5b94fd541..235805dcc 100644 --- a/apps/client/src/app/pages/demo/demo-page.component.ts +++ b/apps/client/src/app/pages/demo/demo-page.component.ts @@ -3,9 +3,8 @@ import { InfoItem } from '@ghostfolio/common/interfaces'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { DataService } from '@ghostfolio/ui/services'; -import { Component, OnDestroy } from '@angular/core'; +import { Component } from '@angular/core'; import { Router } from '@angular/router'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -13,11 +12,9 @@ import { Subject } from 'rxjs'; standalone: true, templateUrl: './demo-page.html' }) -export class GfDemoPageComponent implements OnDestroy { +export class GfDemoPageComponent { public info: InfoItem; - private unsubscribeSubject = new Subject(); - public constructor( private dataService: DataService, private notificationService: NotificationService, @@ -40,9 +37,4 @@ export class GfDemoPageComponent implements OnDestroy { this.router.navigate(['/']); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/faq/faq-page.component.ts b/apps/client/src/app/pages/faq/faq-page.component.ts index caf4217ca..83171254a 100644 --- a/apps/client/src/app/pages/faq/faq-page.component.ts +++ b/apps/client/src/app/pages/faq/faq-page.component.ts @@ -3,19 +3,13 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { DataService } from '@ghostfolio/ui/services'; -import { - CUSTOM_ELEMENTS_SCHEMA, - Component, - OnDestroy, - OnInit -} from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA, Component, OnInit } from '@angular/core'; import { MatTabsModule } from '@angular/material/tabs'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { cloudyOutline, readerOutline, serverOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page has-tabs' }, @@ -25,13 +19,11 @@ import { Subject } from 'rxjs'; styleUrls: ['./faq-page.scss'], templateUrl: './faq-page.html' }) -export class GfFaqPageComponent implements OnDestroy, OnInit { +export class GfFaqPageComponent implements OnInit { public deviceType: string; public hasPermissionForSubscription: boolean; public tabs: TabConfiguration[] = []; - private unsubscribeSubject = new Subject(); - public constructor( private dataService: DataService, private deviceService: DeviceDetectorService @@ -68,9 +60,4 @@ export class GfFaqPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/faq/saas/saas-page.component.ts b/apps/client/src/app/pages/faq/saas/saas-page.component.ts index b47d45fe2..3f44653d2 100644 --- a/apps/client/src/app/pages/faq/saas/saas-page.component.ts +++ b/apps/client/src/app/pages/faq/saas/saas-page.component.ts @@ -7,11 +7,11 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy + DestroyRef } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; -import { Subject, takeUntil } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -21,7 +21,7 @@ import { Subject, takeUntil } from 'rxjs'; styleUrls: ['./saas-page.scss'], templateUrl: './saas-page.html' }) -export class GfSaasPageComponent implements OnDestroy { +export class GfSaasPageComponent { public pricingUrl = `https://ghostfol.io/${document.documentElement.lang}/${publicRoutes.pricing.path}`; public routerLinkAccount = internalRoutes.account.routerLink; public routerLinkAccountMembership = @@ -30,16 +30,15 @@ export class GfSaasPageComponent implements OnDestroy { public routerLinkRegister = publicRoutes.register.routerLink; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private userService: UserService ) {} public ngOnInit() { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -48,9 +47,4 @@ export class GfSaasPageComponent implements OnDestroy { } }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.component.ts b/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.component.ts index ed1d74395..f2468c7d4 100644 --- a/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.component.ts +++ b/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.component.ts @@ -1,10 +1,9 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; -import { CUSTOM_ELEMENTS_SCHEMA, Component, OnDestroy } from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -14,13 +13,6 @@ import { Subject } from 'rxjs'; styleUrls: ['./self-hosting-page.scss'], templateUrl: './self-hosting-page.html' }) -export class GfSelfHostingPageComponent implements OnDestroy { +export class GfSelfHostingPageComponent { public pricingUrl = `https://ghostfol.io/${document.documentElement.lang}/${publicRoutes.pricing.path}`; - - private unsubscribeSubject = new Subject(); - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } 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 42e810eb0..ef88df9aa 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 @@ -93,6 +93,10 @@ dialog +

+ For derived currencies (e.g. GBp), ensure that you + gather the data for the parent currency (e.g. GBP). +

diff --git a/apps/client/src/app/pages/features/features-page.component.ts b/apps/client/src/app/pages/features/features-page.component.ts index b9eb91fe2..cef90f2e5 100644 --- a/apps/client/src/app/pages/features/features-page.component.ts +++ b/apps/client/src/app/pages/features/features-page.component.ts @@ -5,11 +5,11 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { DataService } from '@ghostfolio/ui/services'; -import { ChangeDetectorRef, Component, OnDestroy } from '@angular/core'; +import { ChangeDetectorRef, Component, DestroyRef } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; -import { Subject, takeUntil } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -23,7 +23,7 @@ import { Subject, takeUntil } from 'rxjs'; styleUrls: ['./features-page.scss'], templateUrl: './features-page.html' }) -export class GfFeaturesPageComponent implements OnDestroy { +export class GfFeaturesPageComponent { public hasPermissionForSubscription: boolean; public hasPermissionToCreateUser: boolean; public info: InfoItem; @@ -31,11 +31,10 @@ export class GfFeaturesPageComponent implements OnDestroy { public routerLinkResources = publicRoutes.resources.routerLink; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private userService: UserService ) { this.info = this.dataService.fetchInfo(); @@ -43,7 +42,7 @@ export class GfFeaturesPageComponent implements OnDestroy { public ngOnInit() { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -62,9 +61,4 @@ export class GfFeaturesPageComponent implements OnDestroy { permissions.createUserAccount ); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/home/home-page.component.ts b/apps/client/src/app/pages/home/home-page.component.ts index fd860ced5..5130c8166 100644 --- a/apps/client/src/app/pages/home/home-page.component.ts +++ b/apps/client/src/app/pages/home/home-page.component.ts @@ -8,9 +8,10 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatTabsModule } from '@angular/material/tabs'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; @@ -23,8 +24,6 @@ import { readerOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ host: { class: 'page has-tabs' }, @@ -34,22 +33,21 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./home-page.scss'], templateUrl: './home-page.html' }) -export class GfHomePageComponent implements OnDestroy, OnInit { +export class GfHomePageComponent implements OnInit { public deviceType: string; public hasImpersonationId: boolean; public tabs: TabConfiguration[] = []; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private impersonationStorageService: ImpersonationStorageService, private userService: UserService ) { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -110,14 +108,9 @@ export class GfHomePageComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/i18n/i18n-page.component.ts b/apps/client/src/app/pages/i18n/i18n-page.component.ts index 19ecd222e..76d123914 100644 --- a/apps/client/src/app/pages/i18n/i18n-page.component.ts +++ b/apps/client/src/app/pages/i18n/i18n-page.component.ts @@ -1,5 +1,4 @@ import { Component } from '@angular/core'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -8,11 +7,4 @@ import { Subject } from 'rxjs'; styleUrls: ['./i18n-page.scss'], templateUrl: './i18n-page.html' }) -export class GfI18nPageComponent { - private unsubscribeSubject = new Subject(); - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } -} +export class GfI18nPageComponent {} diff --git a/apps/client/src/app/pages/i18n/i18n-page.html b/apps/client/src/app/pages/i18n/i18n-page.html index b4297d5ac..99ef3039e 100644 --- a/apps/client/src/app/pages/i18n/i18n-page.html +++ b/apps/client/src/app/pages/i18n/i18n-page.html @@ -149,14 +149,14 @@
  • An emergency fund has been set up
  • -
  • Fee Ratio
  • -
  • - The fees do exceed ${thresholdMax}% of your initial investment - (${feeRatio}%) -
  • -
  • - The fees do not exceed ${thresholdMax}% of your initial - investment (${feeRatio}%) +
  • Fee Ratio
  • +
  • + The fees do exceed ${thresholdMax}% of your total investment + volume (${feeRatio}%) +
  • +
  • + The fees do not exceed ${thresholdMax}% of your total + investment volume (${feeRatio}%)
  • Fees
  • diff --git a/apps/client/src/app/pages/landing/landing-page.component.ts b/apps/client/src/app/pages/landing/landing-page.component.ts index 25fb2d6e7..9ee9cceb4 100644 --- a/apps/client/src/app/pages/landing/landing-page.component.ts +++ b/apps/client/src/app/pages/landing/landing-page.component.ts @@ -9,7 +9,7 @@ import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart'; import { CommonModule } from '@angular/common'; -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; @@ -21,7 +21,6 @@ import { starOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -40,7 +39,7 @@ import { Subject } from 'rxjs'; styleUrls: ['./landing-page.scss'], templateUrl: './landing-page.html' }) -export class GfLandingPageComponent implements OnDestroy, OnInit { +export class GfLandingPageComponent implements OnInit { public countriesOfSubscribersMap: { [code: string]: { value: number }; } = {}; @@ -107,8 +106,6 @@ export class GfLandingPageComponent implements OnDestroy, OnInit { } ]; - private unsubscribeSubject = new Subject(); - public constructor( private dataService: DataService, private deviceService: DeviceDetectorService @@ -155,9 +152,4 @@ export class GfLandingPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/markets/markets-page.component.ts b/apps/client/src/app/pages/markets/markets-page.component.ts index 1ab97c3e0..c70cb8120 100644 --- a/apps/client/src/app/pages/markets/markets-page.component.ts +++ b/apps/client/src/app/pages/markets/markets-page.component.ts @@ -1,7 +1,6 @@ import { GfHomeMarketComponent } from '@ghostfolio/client/components/home-market/home-market.component'; -import { Component, OnDestroy } from '@angular/core'; -import { Subject } from 'rxjs'; +import { Component } from '@angular/core'; @Component({ host: { class: 'page' }, @@ -10,11 +9,4 @@ import { Subject } from 'rxjs'; styleUrls: ['./markets-page.scss'], templateUrl: './markets-page.html' }) -export class GfMarketsPageComponent implements OnDestroy { - private unsubscribeSubject = new Subject(); - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } -} +export class GfMarketsPageComponent {} diff --git a/apps/client/src/app/pages/open/open-page.component.ts b/apps/client/src/app/pages/open/open-page.component.ts index 6284c41f4..090588d7d 100644 --- a/apps/client/src/app/pages/open/open-page.component.ts +++ b/apps/client/src/app/pages/open/open-page.component.ts @@ -7,11 +7,11 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatCardModule } from '@angular/material/card'; -import { Subject, takeUntil } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -21,15 +21,14 @@ import { Subject, takeUntil } from 'rxjs'; styleUrls: ['./open-page.scss'], templateUrl: './open-page.html' }) -export class GfOpenPageComponent implements OnDestroy, OnInit { +export class GfOpenPageComponent implements OnInit { public statistics: Statistics; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private userService: UserService ) { const { statistics } = this.dataService.fetchInfo(); @@ -39,7 +38,7 @@ export class GfOpenPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -48,9 +47,4 @@ export class GfOpenPageComponent implements OnDestroy, OnInit { } }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts index cf7a41215..acd6cba10 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts @@ -10,10 +10,17 @@ import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { DateRange } from '@ghostfolio/common/types'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; import { DataService } from '@ghostfolio/ui/services'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + DestroyRef, + OnInit +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { PageEvent } from '@angular/material/paginator'; @@ -26,8 +33,7 @@ import { format, parseISO } from 'date-fns'; import { addIcons } from 'ionicons'; import { addOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject, Subscription } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { Subscription } from 'rxjs'; import { GfCreateOrUpdateActivityDialogComponent } from './create-or-update-activity-dialog/create-or-update-activity-dialog.component'; import { CreateOrUpdateActivityDialogParams } from './create-or-update-activity-dialog/interfaces/interfaces'; @@ -47,7 +53,8 @@ import { ImportActivitiesDialogParams } from './import-activities-dialog/interfa styleUrls: ['./activities-page.scss'], templateUrl: './activities-page.html' }) -export class GfActivitiesPageComponent implements OnDestroy, OnInit { +export class GfActivitiesPageComponent implements OnInit { + public activityTypesFilter: string[] = []; public dataSource: MatTableDataSource; public deviceType: string; public hasImpersonationId: boolean; @@ -58,14 +65,13 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { public routeQueryParams: Subscription; public sortColumn = 'date'; public sortDirection: SortDirection = 'desc'; - public totalItems: number; + public totalItems: number | undefined; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private icsService: IcsService, @@ -75,13 +81,13 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { private userService: UserService ) { this.routeQueryParams = route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if (params['createDialog']) { if (params['activityId']) { this.dataService .fetchActivity(params['activityId']) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((activity) => { this.openCreateActivityDialog(activity); }); @@ -92,7 +98,7 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { if (params['activityId']) { this.dataService .fetchActivity(params['activityId']) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((activity) => { this.openUpdateActivityDialog(activity); }); @@ -110,13 +116,13 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.updateUser(state.user); @@ -129,15 +135,26 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { } public fetchActivities() { + // Reset dataSource and totalItems to show loading state + this.dataSource = undefined; + this.totalItems = undefined; + + const dateRange = this.user?.settings?.dateRange; + const range = this.isCalendarYear(dateRange) ? dateRange : undefined; + this.dataService .fetchActivities({ + range, + activityTypes: this.activityTypesFilter.length + ? this.activityTypesFilter + : undefined, filters: this.userService.getFilters(), skip: this.pageIndex * this.pageSize, sortColumn: this.sortColumn, sortDirection: this.sortDirection, take: this.pageSize }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ activities, count }) => { this.dataSource = new MatTableDataSource(activities); this.totalItems = count; @@ -178,28 +195,32 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { .deleteActivities({ filters: this.userService.getFilters() }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchActivities(); + + this.changeDetectorRef.markForCheck(); }); } public onDeleteActivity(aId: string) { this.dataService .deleteActivity(aId) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchActivities(); + + this.changeDetectorRef.markForCheck(); }); } @@ -207,12 +228,17 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { let fetchExportParams: any = { activityIds }; if (!activityIds) { - fetchExportParams = { filters: this.userService.getFilters() }; + fetchExportParams = { + activityTypes: this.activityTypesFilter.length + ? this.activityTypesFilter + : undefined, + filters: this.userService.getFilters() + }; } this.dataService .fetchExport(fetchExportParams) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { for (const activity of data.activities) { delete activity.id; @@ -232,7 +258,7 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { public onExportDrafts(activityIds?: string[]) { this.dataService .fetchExport({ activityIds }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((data) => { downloadAsFile({ content: this.icsService.transformActivitiesToIcsContent( @@ -262,14 +288,16 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchActivities(); + + this.changeDetectorRef.markForCheck(); }); } @@ -289,14 +317,16 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchActivities(); + + this.changeDetectorRef.markForCheck(); }); } @@ -308,6 +338,13 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { this.fetchActivities(); } + public onTypesFilterChanged(aTypes: string[]) { + this.activityTypesFilter = aTypes; + this.pageIndex = 0; + + this.fetchActivities(); + } + public onUpdateActivity(aActivity: Activity) { this.router.navigate([], { queryParams: { activityId: aActivity.id, editDialog: true } @@ -330,15 +367,17 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((activity: UpdateOrderDto) => { if (activity) { this.dataService - .putOrder(activity) - .pipe(takeUntil(this.unsubscribeSubject)) + .putActivity(activity) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.fetchActivities(); + + this.changeDetectorRef.markForCheck(); } }); } @@ -347,15 +386,18 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); + private isCalendarYear(dateRange: DateRange) { + if (!dateRange) { + return false; + } + + return /^\d{4}$/.test(dateRange); } private openCreateActivityDialog(aActivity?: Activity) { this.userService .get() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.updateUser(user); @@ -382,17 +424,19 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((transaction: CreateOrderDto | null) => { if (transaction) { - this.dataService.postOrder(transaction).subscribe({ + this.dataService.postActivity(transaction).subscribe({ next: () => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.fetchActivities(); + + this.changeDetectorRef.markForCheck(); } }); } @@ -407,9 +451,9 @@ export class GfActivitiesPageComponent implements OnDestroy, OnInit { this.hasPermissionToCreateActivity = !this.hasImpersonationId && - hasPermission(this.user.permissions, permissions.createOrder); + hasPermission(this.user.permissions, permissions.createActivity); this.hasPermissionToDeleteActivity = !this.hasImpersonationId && - hasPermission(this.user.permissions, permissions.deleteOrder); + hasPermission(this.user.permissions, permissions.deleteActivity); } } diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.html b/apps/client/src/app/pages/portfolio/activities/activities-page.html index 80ad71b79..2a72dcfd2 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.html +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -10,6 +10,7 @@ [hasPermissionToCreateActivity]="hasPermissionToCreateActivity" [hasPermissionToDeleteActivity]="hasPermissionToDeleteActivity" [hasPermissionToExportActivities]="!hasImpersonationId" + [hasPermissionToFilterByType]="user?.settings?.isExperimentalFeatures" [locale]="user?.settings?.locale" [pageIndex]="pageIndex" [pageSize]="pageSize" @@ -32,6 +33,7 @@ (importDividends)="onImportDividends()" (pageChanged)="onChangePage($event)" (sortChanged)="onSortChanged($event)" + (typesFilterChanged)="onTypesFilterChanged($event)" />
  • diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts index 8695f04ed..277701862 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts @@ -1,5 +1,6 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { ASSET_CLASS_MAPPING } from '@ghostfolio/common/config'; +import { locale as defaultLocale } from '@ghostfolio/common/config'; import { CreateOrderDto, UpdateOrderDto } from '@ghostfolio/common/dtos'; import { getDateFormatString } from '@ghostfolio/common/helper'; import { @@ -20,9 +21,10 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - Inject, - OnDestroy + DestroyRef, + Inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, FormGroup, @@ -46,8 +48,8 @@ import { AssetClass, Tag, Type } from '@prisma/client'; import { isAfter, isToday } from 'date-fns'; import { addIcons } from 'ionicons'; import { calendarClearOutline, refreshOutline } from 'ionicons/icons'; -import { EMPTY, Subject } from 'rxjs'; -import { catchError, delay, takeUntil } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; +import { catchError, delay } from 'rxjs/operators'; import { CreateOrUpdateActivityDialogParams } from './interfaces/interfaces'; import { ActivityType } from './types/activity-type.type'; @@ -75,7 +77,7 @@ import { ActivityType } from './types/activity-type.type'; styleUrls: ['./create-or-update-activity-dialog.scss'], templateUrl: 'create-or-update-activity-dialog.html' }) -export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { +export class GfCreateOrUpdateActivityDialogComponent { public activityForm: FormGroup; public assetClassOptions: AssetClassSelectorOption[] = Object.keys(AssetClass) @@ -88,11 +90,11 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { public assetSubClassOptions: AssetClassSelectorOption[] = []; public currencies: string[] = []; - public currencyOfAssetProfile: string; - public currentMarketPrice = null; + public currencyOfAssetProfile: string | undefined; + public currentMarketPrice: number | null = null; public defaultDateFormat: string; public defaultLookupItems: LookupItem[] = []; - public hasPermissionToCreateOwnTag: boolean; + public hasPermissionToCreateOwnTag: boolean | undefined; public isLoading = false; public isToday = isToday; public mode: 'create' | 'update'; @@ -101,13 +103,12 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { public typesTranslationMap = new Map(); public Validators = Validators; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateActivityDialogParams, private dataService: DataService, - private dateAdapter: DateAdapter, + private dateAdapter: DateAdapter, + private destroyRef: DestroyRef, public dialogRef: MatDialogRef, private formBuilder: FormBuilder, @Inject(MAT_DATE_LOCALE) private locale: string, @@ -121,7 +122,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { this.hasPermissionToCreateOwnTag = this.data.user?.settings?.isExperimentalFeatures && hasPermission(this.data.user?.permissions, permissions.createOwnTag); - this.locale = this.data.user?.settings?.locale; + this.locale = this.data.user.settings.locale ?? defaultLocale; this.mode = this.data.activity?.id ? 'update' : 'create'; this.dateAdapter.setLocale(this.locale); @@ -133,37 +134,28 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { this.dataService .fetchPortfolioHoldings() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ holdings }) => { this.defaultLookupItems = holdings - .filter(({ assetSubClass }) => { - return !['CASH'].includes(assetSubClass); + .filter(({ assetProfile }) => { + return !['CASH'].includes(assetProfile.assetSubClass); }) .sort((a, b) => { return a.name?.localeCompare(b.name); }) - .map( - ({ - assetClass, - assetSubClass, - currency, - dataSource, - name, - symbol - }) => { - return { - assetClass, - assetSubClass, - currency, - dataSource, - name, - symbol, - dataProviderInfo: { - isPremium: false - } - }; - } - ); + .map(({ assetProfile }) => { + return { + assetClass: assetProfile.assetClass, + assetSubClass: assetProfile.assetSubClass, + currency: assetProfile.currency ?? '', + dataProviderInfo: { + isPremium: false + }, + dataSource: assetProfile.dataSource, + name: assetProfile.name ?? '', + symbol: assetProfile.symbol + }; + }); this.changeDetectorRef.markForCheck(); }); @@ -188,8 +180,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { !this.data.activity?.accountId && this.mode === 'create' ? this.data.accounts[0].id - : this.data.activity?.accountId, - Validators.required + : this.data.activity?.accountId ], assetClass: [this.data.activity?.SymbolProfile?.assetClass], assetSubClass: [this.data.activity?.SymbolProfile?.assetSubClass], @@ -238,30 +229,30 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { // Slightly delay until the more specific form control value changes have // completed delay(300), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(async () => { if ( ['BUY', 'FEE', 'VALUABLE'].includes( - this.activityForm.get('type').value + this.activityForm.get('type')?.value ) ) { this.total = - this.activityForm.get('quantity').value * - this.activityForm.get('unitPrice').value + - (this.activityForm.get('fee').value ?? 0); + this.activityForm.get('quantity')?.value * + this.activityForm.get('unitPrice')?.value + + (this.activityForm.get('fee')?.value ?? 0); } else { this.total = - this.activityForm.get('quantity').value * - this.activityForm.get('unitPrice').value - - (this.activityForm.get('fee').value ?? 0); + this.activityForm.get('quantity')?.value * + this.activityForm.get('unitPrice')?.value - + (this.activityForm.get('fee')?.value ?? 0); } this.changeDetectorRef.markForCheck(); }); - this.activityForm.get('accountId').valueChanges.subscribe((accountId) => { - const type = this.activityForm.get('type').value; + this.activityForm.get('accountId')?.valueChanges.subscribe((accountId) => { + const type = this.activityForm.get('type')?.value; if (['FEE', 'INTEREST', 'LIABILITY', 'VALUABLE'].includes(type)) { const currency = @@ -269,15 +260,15 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { return id === accountId; })?.currency ?? this.data.user.settings.baseCurrency; - this.activityForm.get('currency').setValue(currency); - this.activityForm.get('currencyOfUnitPrice').setValue(currency); + this.activityForm.get('currency')?.setValue(currency); + this.activityForm.get('currencyOfUnitPrice')?.setValue(currency); if (['FEE', 'INTEREST'].includes(type)) { - if (this.activityForm.get('accountId').value) { - this.activityForm.get('updateAccountBalance').enable(); + if (this.activityForm.get('accountId')?.value) { + this.activityForm.get('updateAccountBalance')?.enable(); } else { - this.activityForm.get('updateAccountBalance').disable(); - this.activityForm.get('updateAccountBalance').setValue(false); + this.activityForm.get('updateAccountBalance')?.disable(); + this.activityForm.get('updateAccountBalance')?.setValue(false); } } } @@ -285,7 +276,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { this.activityForm .get('assetClass') - .valueChanges.pipe(takeUntil(this.unsubscribeSubject)) + ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((assetClass) => { const assetSubClasses = ASSET_CLASS_MAPPING.get(assetClass) ?? []; @@ -298,28 +289,28 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { }) .sort((a, b) => a.label.localeCompare(b.label)); - this.activityForm.get('assetSubClass').setValue(null); + this.activityForm.get('assetSubClass')?.setValue(null); this.changeDetectorRef.markForCheck(); }); - this.activityForm.get('date').valueChanges.subscribe(() => { - if (isToday(this.activityForm.get('date').value)) { - this.activityForm.get('updateAccountBalance').enable(); + this.activityForm.get('date')?.valueChanges.subscribe(() => { + if (isToday(this.activityForm.get('date')?.value)) { + this.activityForm.get('updateAccountBalance')?.enable(); } else { - this.activityForm.get('updateAccountBalance').disable(); - this.activityForm.get('updateAccountBalance').setValue(false); + this.activityForm.get('updateAccountBalance')?.disable(); + this.activityForm.get('updateAccountBalance')?.setValue(false); } this.changeDetectorRef.markForCheck(); }); - this.activityForm.get('searchSymbol').valueChanges.subscribe(() => { - if (this.activityForm.get('searchSymbol').invalid) { + this.activityForm.get('searchSymbol')?.valueChanges.subscribe(() => { + if (this.activityForm.get('searchSymbol')?.invalid) { this.data.activity.SymbolProfile = null; } else if ( ['BUY', 'DIVIDEND', 'SELL'].includes( - this.activityForm.get('type').value + this.activityForm.get('type')?.value ) ) { this.updateAssetProfile(); @@ -328,7 +319,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { this.changeDetectorRef.markForCheck(); }); - this.activityForm.get('tags').valueChanges.subscribe((tags: Tag[]) => { + this.activityForm.get('tags')?.valueChanges.subscribe((tags: Tag[]) => { const newTag = tags.find(({ id }) => { return id === undefined; }); @@ -336,9 +327,9 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { if (newTag && this.hasPermissionToCreateOwnTag) { this.dataService .postTag({ ...newTag, userId: this.data.user.id }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((tag) => { - this.activityForm.get('tags').setValue( + this.activityForm.get('tags')?.setValue( tags.map((currentTag) => { if (currentTag.id === undefined) { return tag; @@ -350,7 +341,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); }); } @@ -358,118 +349,106 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { this.activityForm .get('type') - .valueChanges.pipe(takeUntil(this.unsubscribeSubject)) + ?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((type: ActivityType) => { if ( type === 'VALUABLE' || - (this.activityForm.get('dataSource').value === 'MANUAL' && + (this.activityForm.get('dataSource')?.value === 'MANUAL' && type === 'BUY') ) { - this.activityForm - .get('accountId') - .removeValidators(Validators.required); - this.activityForm.get('accountId').updateValueAndValidity(); - const currency = this.data.accounts.find(({ id }) => { - return id === this.activityForm.get('accountId').value; + return id === this.activityForm.get('accountId')?.value; })?.currency ?? this.data.user.settings.baseCurrency; - this.activityForm.get('currency').setValue(currency); - this.activityForm.get('currencyOfUnitPrice').setValue(currency); + this.activityForm.get('currency')?.setValue(currency); + this.activityForm.get('currencyOfUnitPrice')?.setValue(currency); this.activityForm .get('dataSource') - .removeValidators(Validators.required); - this.activityForm.get('dataSource').updateValueAndValidity(); - this.activityForm.get('fee').setValue(0); - this.activityForm.get('name').setValidators(Validators.required); - this.activityForm.get('name').updateValueAndValidity(); + ?.removeValidators(Validators.required); + this.activityForm.get('dataSource')?.updateValueAndValidity(); + this.activityForm.get('fee')?.setValue(0); + this.activityForm.get('name')?.setValidators(Validators.required); + this.activityForm.get('name')?.updateValueAndValidity(); if (type === 'VALUABLE') { - this.activityForm.get('quantity').setValue(1); + this.activityForm.get('quantity')?.setValue(1); } this.activityForm .get('searchSymbol') - .removeValidators(Validators.required); - this.activityForm.get('searchSymbol').updateValueAndValidity(); - this.activityForm.get('updateAccountBalance').disable(); - this.activityForm.get('updateAccountBalance').setValue(false); + ?.removeValidators(Validators.required); + this.activityForm.get('searchSymbol')?.updateValueAndValidity(); + this.activityForm.get('updateAccountBalance')?.disable(); + this.activityForm.get('updateAccountBalance')?.setValue(false); } else if (['FEE', 'INTEREST', 'LIABILITY'].includes(type)) { - this.activityForm - .get('accountId') - .removeValidators(Validators.required); - this.activityForm.get('accountId').updateValueAndValidity(); - const currency = this.data.accounts.find(({ id }) => { - return id === this.activityForm.get('accountId').value; + return id === this.activityForm.get('accountId')?.value; })?.currency ?? this.data.user.settings.baseCurrency; - this.activityForm.get('currency').setValue(currency); - this.activityForm.get('currencyOfUnitPrice').setValue(currency); + this.activityForm.get('currency')?.setValue(currency); + this.activityForm.get('currencyOfUnitPrice')?.setValue(currency); this.activityForm .get('dataSource') - .removeValidators(Validators.required); - this.activityForm.get('dataSource').updateValueAndValidity(); + ?.removeValidators(Validators.required); + this.activityForm.get('dataSource')?.updateValueAndValidity(); if (['INTEREST', 'LIABILITY'].includes(type)) { - this.activityForm.get('fee').setValue(0); + this.activityForm.get('fee')?.setValue(0); } - this.activityForm.get('name').setValidators(Validators.required); - this.activityForm.get('name').updateValueAndValidity(); + this.activityForm.get('name')?.setValidators(Validators.required); + this.activityForm.get('name')?.updateValueAndValidity(); if (type === 'FEE') { - this.activityForm.get('quantity').setValue(0); + this.activityForm.get('quantity')?.setValue(0); } else if (['INTEREST', 'LIABILITY'].includes(type)) { - this.activityForm.get('quantity').setValue(1); + this.activityForm.get('quantity')?.setValue(1); } this.activityForm .get('searchSymbol') - .removeValidators(Validators.required); - this.activityForm.get('searchSymbol').updateValueAndValidity(); + ?.removeValidators(Validators.required); + this.activityForm.get('searchSymbol')?.updateValueAndValidity(); if (type === 'FEE') { - this.activityForm.get('unitPrice').setValue(0); + this.activityForm.get('unitPrice')?.setValue(0); } if ( ['FEE', 'INTEREST'].includes(type) && - this.activityForm.get('accountId').value + this.activityForm.get('accountId')?.value ) { - this.activityForm.get('updateAccountBalance').enable(); + this.activityForm.get('updateAccountBalance')?.enable(); } else { - this.activityForm.get('updateAccountBalance').disable(); - this.activityForm.get('updateAccountBalance').setValue(false); + this.activityForm.get('updateAccountBalance')?.disable(); + this.activityForm.get('updateAccountBalance')?.setValue(false); } } else { - this.activityForm.get('accountId').setValidators(Validators.required); - this.activityForm.get('accountId').updateValueAndValidity(); this.activityForm .get('dataSource') - .setValidators(Validators.required); - this.activityForm.get('dataSource').updateValueAndValidity(); - this.activityForm.get('name').removeValidators(Validators.required); - this.activityForm.get('name').updateValueAndValidity(); + ?.setValidators(Validators.required); + this.activityForm.get('dataSource')?.updateValueAndValidity(); + this.activityForm.get('name')?.removeValidators(Validators.required); + this.activityForm.get('name')?.updateValueAndValidity(); this.activityForm .get('searchSymbol') - .setValidators(Validators.required); - this.activityForm.get('searchSymbol').updateValueAndValidity(); - this.activityForm.get('updateAccountBalance').enable(); + ?.setValidators(Validators.required); + this.activityForm.get('searchSymbol')?.updateValueAndValidity(); + this.activityForm.get('updateAccountBalance')?.enable(); } this.changeDetectorRef.markForCheck(); }); - this.activityForm.get('type').setValue(this.data.activity?.type); + this.activityForm.get('type')?.setValue(this.data.activity?.type); if (this.data.activity?.id) { - this.activityForm.get('searchSymbol').disable(); - this.activityForm.get('type').disable(); + this.activityForm.get('searchSymbol')?.disable(); + this.activityForm.get('type')?.disable(); } if (this.data.activity?.SymbolProfile?.symbol) { @@ -478,7 +457,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { dataSource: this.data.activity?.SymbolProfile?.dataSource, symbol: this.data.activity?.SymbolProfile?.symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ marketPrice }) => { this.currentMarketPrice = marketPrice; @@ -489,7 +468,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { public applyCurrentMarketPrice() { this.activityForm.patchValue({ - currencyOfUnitPrice: this.activityForm.get('currency').value, + currencyOfUnitPrice: this.activityForm.get('currency')?.value, unitPrice: this.currentMarketPrice }); } @@ -508,41 +487,42 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { public async onSubmit() { const activity: CreateOrderDto | UpdateOrderDto = { - accountId: this.activityForm.get('accountId').value, - assetClass: this.activityForm.get('assetClass').value, - assetSubClass: this.activityForm.get('assetSubClass').value, - comment: this.activityForm.get('comment').value || null, - currency: this.activityForm.get('currency').value, - customCurrency: this.activityForm.get('currencyOfUnitPrice').value, - date: this.activityForm.get('date').value, - dataSource: - this.activityForm.get('type').value === 'VALUABLE' - ? 'MANUAL' - : this.activityForm.get('dataSource').value, - fee: this.activityForm.get('fee').value, - quantity: this.activityForm.get('quantity').value, + accountId: this.activityForm.get('accountId')?.value, + assetClass: this.activityForm.get('assetClass')?.value, + assetSubClass: this.activityForm.get('assetSubClass')?.value, + comment: this.activityForm.get('comment')?.value || null, + currency: this.activityForm.get('currency')?.value, + customCurrency: this.activityForm.get('currencyOfUnitPrice')?.value, + dataSource: ['FEE', 'INTEREST', 'LIABILITY', 'VALUABLE'].includes( + this.activityForm.get('type')?.value + ) + ? 'MANUAL' + : this.activityForm.get('dataSource')?.value, + date: this.activityForm.get('date')?.value, + fee: this.activityForm.get('fee')?.value, + quantity: this.activityForm.get('quantity')?.value, symbol: (['FEE', 'INTEREST', 'LIABILITY', 'VALUABLE'].includes( - this.activityForm.get('type').value + this.activityForm.get('type')?.value ) ? undefined : this.activityForm.get('searchSymbol')?.value?.symbol) ?? this.activityForm.get('name')?.value, - tags: this.activityForm.get('tags').value?.map(({ id }) => { + tags: this.activityForm.get('tags')?.value?.map(({ id }) => { return id; }), type: - this.activityForm.get('type').value === 'VALUABLE' + this.activityForm.get('type')?.value === 'VALUABLE' ? 'BUY' - : this.activityForm.get('type').value, - unitPrice: this.activityForm.get('unitPrice').value + : this.activityForm.get('type')?.value, + unitPrice: this.activityForm.get('unitPrice')?.value }; try { if (this.mode === 'create') { activity.updateAccountBalance = this.activityForm.get( 'updateAccountBalance' - ).value; + )?.value; await validateObjectForForm({ classDto: CreateOrderDto, @@ -569,19 +549,14 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { } } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private updateAssetProfile() { this.isLoading = true; this.changeDetectorRef.markForCheck(); this.dataService .fetchSymbolItem({ - dataSource: this.activityForm.get('searchSymbol').value.dataSource, - symbol: this.activityForm.get('searchSymbol').value.symbol + dataSource: this.activityForm.get('searchSymbol')?.value.dataSource, + symbol: this.activityForm.get('searchSymbol')?.value.symbol }) .pipe( catchError(() => { @@ -593,13 +568,13 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(({ currency, dataSource, marketPrice }) => { if (this.mode === 'create') { - this.activityForm.get('currency').setValue(currency); - this.activityForm.get('currencyOfUnitPrice').setValue(currency); - this.activityForm.get('dataSource').setValue(dataSource); + this.activityForm.get('currency')?.setValue(currency); + this.activityForm.get('currencyOfUnitPrice')?.setValue(currency); + this.activityForm.get('dataSource')?.setValue(dataSource); } this.currencyOfAssetProfile = currency; diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html index 42fbd0ebf..038da44cb 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -15,7 +15,7 @@ Type {{ - typesTranslationMap[activityForm.get('type').value] + typesTranslationMap[activityForm.get('type')?.value] }} Account - @if ( - !activityForm.get('accountId').hasValidator(Validators.required) || - (!activityForm.get('accountId').value && mode === 'update') - ) { - - } + + @for (account of data.accounts; track account) {
    @@ -117,7 +113,7 @@ [ngClass]="{ 'd-none': !activityForm .get('searchSymbol') - .hasValidator(Validators.required) + ?.hasValidator(Validators.required) }" > @@ -132,7 +128,7 @@
    @@ -232,7 +228,7 @@ @if ( currencyOfAssetProfile === - activityForm.get('currencyOfUnitPrice').value && + activityForm.get('currencyOfUnitPrice')?.value && currentMarketPrice && ['BUY', 'SELL'].includes(data.activity.type) && isToday(activityForm.get('date')?.value) @@ -266,7 +262,7 @@ matTextSuffix [ngClass]="{ 'd-none': !activityForm.get('currency')?.value }" > - {{ activityForm.get('currencyOfUnitPrice').value }} + {{ activityForm.get('currencyOfUnitPrice')?.value }}
    diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts index 5206aacf9..322bcc076 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/interfaces/interfaces.ts @@ -4,6 +4,8 @@ import { Account } from '@prisma/client'; export interface CreateOrUpdateActivityDialogParams { accounts: Account[]; - activity: Activity; + activity: Omit & { + SymbolProfile: Activity['SymbolProfile'] | null; + }; user: User; } diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts index 1a84e9f31..00f0508fe 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts @@ -21,9 +21,10 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - Inject, - OnDestroy + DestroyRef, + Inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, FormGroup, @@ -52,7 +53,6 @@ import { cloudUploadOutline, warningOutline } from 'ionicons/icons'; import { isArray, sortBy } from 'lodash'; import ms from 'ms'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject, takeUntil } from 'rxjs'; import { ImportStep } from './enums/import-step'; import { ImportActivitiesDialogParams } from './interfaces/interfaces'; @@ -81,7 +81,7 @@ import { ImportActivitiesDialogParams } from './interfaces/interfaces'; styleUrls: ['./import-activities-dialog.scss'], templateUrl: 'import-activities-dialog.html' }) -export class GfImportActivitiesDialogComponent implements OnDestroy { +export class GfImportActivitiesDialogComponent { public accounts: CreateAccountWithBalancesDto[] = []; public activities: Activity[] = []; public assetProfileForm: FormGroup; @@ -104,12 +104,11 @@ export class GfImportActivitiesDialogComponent implements OnDestroy { public tags: CreateTagDto[] = []; public totalItems: number; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: ImportActivitiesDialogParams, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private formBuilder: FormBuilder, public dialogRef: MatDialogRef, @@ -152,7 +151,7 @@ export class GfImportActivitiesDialogComponent implements OnDestroy { ], range: 'max' }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ holdings }) => { this.holdings = sortBy(holdings, ({ name }) => { return name.toLowerCase(); @@ -237,7 +236,7 @@ export class GfImportActivitiesDialogComponent implements OnDestroy { dataSource, symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ activities }) => { this.activities = activities; this.dataSource = new MatTableDataSource(activities.reverse()); @@ -284,11 +283,6 @@ export class GfImportActivitiesDialogComponent implements OnDestroy { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private async handleFile({ file, stepper diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index 70fa09eb1..b0282c937 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -22,7 +22,13 @@ import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart'; import { NgClass } from '@angular/common'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + DestroyRef, + OnInit +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatCardModule } from '@angular/material/card'; import { MatDialog } from '@angular/material/dialog'; import { MatProgressBarModule } from '@angular/material/progress-bar'; @@ -36,8 +42,6 @@ import { } from '@prisma/client'; import { isNumber } from 'lodash'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [ @@ -54,7 +58,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./allocations-page.scss'], templateUrl: './allocations-page.html' }) -export class GfAllocationsPageComponent implements OnDestroy, OnInit { +export class GfAllocationsPageComponent implements OnInit { public accounts: { [id: string]: Pick & { id: string; @@ -119,11 +123,10 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { public user: User; public worldMapChartFormat: string; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private impersonationStorageService: ImpersonationStorageService, @@ -132,7 +135,7 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { private userService: UserService ) { this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if (params['accountId'] && params['accountDetailDialog']) { this.openAccountDetailDialog(params['accountId']); @@ -145,28 +148,27 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; - this.worldMapChartFormat = - this.hasImpersonationId || this.user.settings.isRestrictedView - ? `{0}%` - : `{0} ${this.user?.settings?.baseCurrency}`; + this.worldMapChartFormat = this.showValuesInPercentage() + ? `{0}%` + : `{0} ${this.user?.settings?.baseCurrency}`; this.isLoading = true; this.initialize(); this.fetchPortfolioDetails() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((portfolioDetails) => { this.initialize(); @@ -202,11 +204,6 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { } } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private extractEtfProvider({ assetSubClass, name @@ -312,7 +309,7 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { ] of Object.entries(this.portfolioDetails.accounts)) { let value = 0; - if (this.hasImpersonationId) { + if (this.showValuesInPercentage()) { value = valueInPercentage; } else { value = valueInBaseCurrency; @@ -330,7 +327,7 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { )) { let value = 0; - if (this.hasImpersonationId) { + if (this.showValuesInPercentage()) { value = position.allocationInPercentage; } else { value = position.valueInBaseCurrency; @@ -407,17 +404,22 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { } if (position.holdings.length > 0) { - for (const holding of position.holdings) { - const { allocationInPercentage, name, valueInBaseCurrency } = - holding; - - if (this.topHoldingsMap[name]?.value) { - this.topHoldingsMap[name].value += isNumber(valueInBaseCurrency) + for (const { + allocationInPercentage, + name, + valueInBaseCurrency + } of position.holdings) { + const normalizedAssetName = this.normalizeAssetName(name); + + if (this.topHoldingsMap[normalizedAssetName]?.value) { + this.topHoldingsMap[normalizedAssetName].value += isNumber( + valueInBaseCurrency + ) ? valueInBaseCurrency : allocationInPercentage * this.portfolioDetails.holdings[symbol].valueInPercentage; } else { - this.topHoldingsMap[name] = { + this.topHoldingsMap[normalizedAssetName] = { name, value: isNumber(valueInBaseCurrency) ? valueInBaseCurrency @@ -488,7 +490,7 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { ] of Object.entries(this.portfolioDetails.platforms)) { let value = 0; - if (this.hasImpersonationId) { + if (this.showValuesInPercentage()) { value = valueInPercentage; } else { value = valueInBaseCurrency; @@ -503,7 +505,7 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { this.topHoldings = Object.values(this.topHoldingsMap) .map(({ name, value }) => { - if (this.hasImpersonationId || this.user.settings.isRestrictedView) { + if (this.showValuesInPercentage()) { return { name, allocationInPercentage: value, @@ -520,7 +522,10 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { if (holding.holdings.length > 0) { const currentParentHolding = holding.holdings.find( (parentHolding) => { - return parentHolding.name === name; + return ( + this.normalizeAssetName(parentHolding.name) === + this.normalizeAssetName(name) + ); } ); @@ -557,6 +562,14 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { } } + private normalizeAssetName(name: string) { + if (!name) { + return ''; + } + + return name.trim().toLowerCase(); + } + private openAccountDetailDialog(aAccountId: string) { const dialogRef = this.dialog.open< GfAccountDetailDialogComponent, @@ -569,7 +582,7 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { hasImpersonationId: this.hasImpersonationId, hasPermissionToCreateActivity: !this.hasImpersonationId && - hasPermission(this.user?.permissions, permissions.createOrder) && + hasPermission(this.user?.permissions, permissions.createActivity) && !this.user?.settings?.isRestrictedView }, height: this.deviceType === 'mobile' ? '98vh' : '80vh', @@ -578,9 +591,13 @@ export class GfAllocationsPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.router.navigate(['.'], { relativeTo: this.route }); }); } + + public showValuesInPercentage() { + return this.hasImpersonationId || this.user?.settings?.isRestrictedView; + } } diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.html b/apps/client/src/app/pages/portfolio/allocations/allocations-page.html index 8d5503840..33431ce5d 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.html +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.html @@ -25,11 +25,9 @@ @@ -49,7 +47,7 @@ [baseCurrency]="user?.settings?.baseCurrency" [colorScheme]="user?.settings?.colorScheme" [data]="platforms" - [isInPercent]="hasImpersonationId || user.settings.isRestrictedView" + [isInPercentage]="showValuesInPercentage()" [keys]="['id']" [locale]="user?.settings?.locale" /> @@ -71,7 +69,7 @@ [baseCurrency]="user?.settings?.baseCurrency" [colorScheme]="user?.settings?.colorScheme" [data]="holdings" - [isInPercent]="hasImpersonationId || user.settings.isRestrictedView" + [isInPercentage]="showValuesInPercentage()" [keys]="['currency']" [locale]="user?.settings?.locale" /> @@ -93,7 +91,7 @@ [baseCurrency]="user?.settings?.baseCurrency" [colorScheme]="user?.settings?.colorScheme" [data]="holdings" - [isInPercent]="hasImpersonationId || user.settings.isRestrictedView" + [isInPercentage]="showValuesInPercentage()" [keys]="['assetClassLabel', 'assetSubClassLabel']" [locale]="user?.settings?.locale" /> @@ -114,7 +112,7 @@ [baseCurrency]="user?.settings?.baseCurrency" [colorScheme]="user?.settings?.colorScheme" [data]="symbols" - [isInPercent]="hasImpersonationId || user.settings.isRestrictedView" + [isInPercentage]="showValuesInPercentage()" [keys]="['symbol']" [locale]="user?.settings?.locale" [showLabels]="deviceType !== 'mobile'" @@ -138,7 +136,7 @@ [baseCurrency]="user?.settings?.baseCurrency" [colorScheme]="user?.settings?.colorScheme" [data]="sectors" - [isInPercent]="hasImpersonationId || user.settings.isRestrictedView" + [isInPercentage]="showValuesInPercentage()" [keys]="['name']" [locale]="user?.settings?.locale" [maxItems]="10" @@ -161,7 +159,7 @@ [baseCurrency]="user?.settings?.baseCurrency" [colorScheme]="user?.settings?.colorScheme" [data]="continents" - [isInPercent]="hasImpersonationId || user.settings.isRestrictedView" + [isInPercentage]="showValuesInPercentage()" [keys]="['name']" [locale]="user?.settings?.locale" /> @@ -183,7 +181,7 @@ [baseCurrency]="user?.settings?.baseCurrency" [colorScheme]="user?.settings?.colorScheme" [data]="marketsAdvanced" - [isInPercent]="hasImpersonationId || user.settings.isRestrictedView" + [isInPercentage]="showValuesInPercentage()" [locale]="user?.settings?.locale" /> @@ -206,9 +204,7 @@
    @@ -272,7 +268,7 @@ [baseCurrency]="user?.settings?.baseCurrency" [colorScheme]="user?.settings?.colorScheme" [data]="countries" - [isInPercent]="hasImpersonationId || user.settings.isRestrictedView" + [isInPercentage]="showValuesInPercentage()" [keys]="['name']" [locale]="user?.settings?.locale" [maxItems]="10" @@ -291,7 +287,7 @@ [baseCurrency]="user?.settings?.baseCurrency" [colorScheme]="user?.settings?.colorScheme" [data]="accounts" - [isInPercent]="hasImpersonationId || user.settings.isRestrictedView" + [isInPercentage]="showValuesInPercentage()" [keys]="['id']" [locale]="user?.settings?.locale" (proportionChartClicked)="onAccountChartClicked($event)" @@ -314,7 +310,7 @@ [baseCurrency]="user?.settings?.baseCurrency" [colorScheme]="user?.settings?.colorScheme" [data]="holdings" - [isInPercent]="hasImpersonationId || user.settings.isRestrictedView" + [isInPercentage]="showValuesInPercentage()" [keys]="['etfProvider']" [locale]="user?.settings?.locale" /> diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts index 5cd24777c..47845ea6f 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts @@ -24,10 +24,11 @@ import { Clipboard } from '@angular/cdk/clipboard'; import { ChangeDetectorRef, Component, - OnDestroy, + DestroyRef, OnInit, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu'; @@ -42,8 +43,6 @@ import { isNumber, sortBy } from 'lodash'; import ms from 'ms'; import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [ @@ -64,7 +63,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./analysis-page.scss'], templateUrl: './analysis-page.html' }) -export class GfAnalysisPageComponent implements OnDestroy, OnInit { +export class GfAnalysisPageComponent implements OnInit { @ViewChild(MatMenuTrigger) actionsMenuButton!: MatMenuTrigger; public benchmark: Partial; @@ -102,12 +101,11 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { public unitLongestStreak: string; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private clipboard: Clipboard, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private impersonationStorageService: ImpersonationStorageService, private snackBar: MatSnackBar, @@ -135,13 +133,13 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -163,11 +161,11 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { public onChangeBenchmark(symbolProfileId: string) { this.dataService .putUserSetting({ benchmark: symbolProfileId }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -193,7 +191,7 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { mode, filters: this.userService.getFilters() }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ prompt }) => { this.clipboard.copy(prompt); @@ -207,7 +205,7 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { snackBarRef .onAction() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { window.open('https://duck.ai', '_blank'); }); @@ -222,11 +220,6 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private fetchDividendsAndInvestments() { this.isLoadingDividendTimelineChart = true; this.isLoadingInvestmentTimelineChart = true; @@ -237,7 +230,7 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { groupBy: this.mode, range: this.user?.settings?.dateRange }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ dividends }) => { this.dividendsByGroup = dividends; @@ -252,7 +245,7 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { groupBy: this.mode, range: this.user?.settings?.dateRange }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ investments, streaks }) => { this.investmentsByGroup = investments; this.streaks = streaks; @@ -287,7 +280,7 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { filters: this.userService.getFilters(), range: this.user?.settings?.dateRange }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ chart, firstOrderDate, performance }) => { this.firstOrderDate = firstOrderDate ?? new Date(); @@ -346,7 +339,7 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { filters: this.userService.getFilters(), range: this.user?.settings?.dateRange }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ holdings }) => { const holdingsSorted = sortBy( holdings.filter(({ netPerformancePercentWithCurrencyEffect }) => { @@ -397,7 +390,7 @@ export class GfAnalysisPageComponent implements OnDestroy, OnInit { range: this.user?.settings?.dateRange, startDate: this.firstOrderDate }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ marketData }) => { this.benchmarkDataItems = marketData.map(({ date, value }) => { return { diff --git a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html index 517ad7101..c4e8f610b 100644 --- a/apps/client/src/app/pages/portfolio/analysis/analysis-page.html +++ b/apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -417,7 +417,9 @@ [benchmarkDataLabel]="portfolioEvolutionDataLabel" [currency]="user?.settings?.baseCurrency" [historicalDataItems]="performanceDataItems" - [isInPercent]="hasImpersonationId || user.settings.isRestrictedView" + [isInPercentage]=" + hasImpersonationId || user.settings.isRestrictedView + " [isLoading]="isLoadingInvestmentChart" [locale]="user?.settings?.locale" /> @@ -473,7 +475,9 @@ [benchmarkDataLabel]="investmentTimelineDataLabel" [currency]="user?.settings?.baseCurrency" [groupBy]="mode" - [isInPercent]="hasImpersonationId || user.settings.isRestrictedView" + [isInPercentage]=" + hasImpersonationId || user.settings.isRestrictedView + " [isLoading]="isLoadingInvestmentTimelineChart" [locale]="user?.settings?.locale" [savingsRate]="savingsRate" @@ -508,7 +512,9 @@ [benchmarkDataLabel]="dividendTimelineDataLabel" [currency]="user?.settings?.baseCurrency" [groupBy]="mode" - [isInPercent]="hasImpersonationId || user.settings.isRestrictedView" + [isInPercentage]=" + hasImpersonationId || user.settings.isRestrictedView + " [isLoading]="isLoadingDividendTimelineChart" [locale]="user?.settings?.locale" /> diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts index 27db6c76e..2f7568982 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts @@ -12,14 +12,18 @@ import { DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { CommonModule, NgStyle } from '@angular/common'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + DestroyRef, + OnInit +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormControl } from '@angular/forms'; import { Big } from 'big.js'; import { DeviceDetectorService } from 'ngx-device-detector'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ imports: [ @@ -36,7 +40,7 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./fire-page.scss'], templateUrl: './fire-page.html' }) -export class GfFirePageComponent implements OnDestroy, OnInit { +export class GfFirePageComponent implements OnInit { public deviceType: string; public fireWealth: FireWealth; public hasImpersonationId: boolean; @@ -52,11 +56,10 @@ export class GfFirePageComponent implements OnDestroy, OnInit { public withdrawalRatePerYear: Big; public withdrawalRatePerYearProjected: Big; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private impersonationStorageService: ImpersonationStorageService, private userService: UserService @@ -68,7 +71,7 @@ export class GfFirePageComponent implements OnDestroy, OnInit { this.dataService .fetchPortfolioDetails() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ summary }) => { this.fireWealth = { today: { @@ -92,19 +95,19 @@ export class GfFirePageComponent implements OnDestroy, OnInit { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); this.safeWithdrawalRateControl.valueChanges - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((value) => { this.onSafeWithdrawalRateChange(Number(value)); }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -132,11 +135,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit { public onAnnualInterestRateChange(annualInterestRate: number) { this.dataService .putUserSetting({ annualInterestRate }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -163,11 +166,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit { retirementDate: retirementDate.toISOString(), projectedTotalAmount: null }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -179,11 +182,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit { public onSafeWithdrawalRateChange(safeWithdrawalRate: number) { this.dataService .putUserSetting({ safeWithdrawalRate }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -198,11 +201,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit { public onSavingsRateChange(savingsRate: number) { this.dataService .putUserSetting({ savingsRate }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -217,11 +220,11 @@ export class GfFirePageComponent implements OnDestroy, OnInit { projectedTotalAmount, retirementDate: null }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((user) => { this.user = user; @@ -230,11 +233,6 @@ export class GfFirePageComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private calculateWithdrawalRates() { if (this.fireWealth && this.user?.settings?.safeWithdrawalRate) { this.withdrawalRatePerYear = new Big( diff --git a/apps/client/src/app/pages/portfolio/portfolio-page.component.ts b/apps/client/src/app/pages/portfolio/portfolio-page.component.ts index 387e3fd0f..eb4935127 100644 --- a/apps/client/src/app/pages/portfolio/portfolio-page.component.ts +++ b/apps/client/src/app/pages/portfolio/portfolio-page.component.ts @@ -2,7 +2,13 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { TabConfiguration, User } from '@ghostfolio/common/interfaces'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + DestroyRef, + OnInit +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatTabsModule } from '@angular/material/tabs'; import { RouterModule } from '@angular/router'; import { addIcons } from 'ionicons'; @@ -14,8 +20,6 @@ import { swapVerticalOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ host: { class: 'page has-tabs' }, @@ -24,20 +28,19 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./portfolio-page.scss'], templateUrl: './portfolio-page.html' }) -export class PortfolioPageComponent implements OnDestroy, OnInit { +export class PortfolioPageComponent implements OnInit { public deviceType: string; public tabs: TabConfiguration[] = []; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private userService: UserService ) { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.tabs = [ @@ -87,9 +90,4 @@ export class PortfolioPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts index 70b748b10..e97fd4876 100644 --- a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts +++ b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts @@ -12,7 +12,8 @@ import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { DataService } from '@ghostfolio/ui/services'; import { NgClass } from '@angular/common'; -import { ChangeDetectorRef, Component } from '@angular/core'; +import { ChangeDetectorRef, Component, DestroyRef } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { @@ -21,7 +22,6 @@ import { warningOutline } from 'ionicons/icons'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject, takeUntil } from 'rxjs'; @Component({ imports: [ @@ -48,11 +48,10 @@ export class GfXRayPageComponent { public statistics: PortfolioReportResponse['xRay']['statistics']; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private impersonationStorageService: ImpersonationStorageService, private userService: UserService ) { @@ -62,13 +61,13 @@ export class GfXRayPageComponent { public ngOnInit() { this.impersonationStorageService .onChangeHasImpersonation() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -91,28 +90,23 @@ export class GfXRayPageComponent { public onRulesUpdated(event: UpdateUserSettingDto) { this.dataService .putUserSetting(event) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.userService .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(); this.initializePortfolioReport(); }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private initializePortfolioReport() { this.isLoading = true; this.dataService .fetchPortfolioReport() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ xRay: { categories, statistics } }) => { this.categories = categories; this.inactiveRules = this.mergeInactiveRules(categories); diff --git a/apps/client/src/app/pages/pricing/pricing-page.component.ts b/apps/client/src/app/pages/pricing/pricing-page.component.ts index 831f0809c..08c561fc8 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.component.ts +++ b/apps/client/src/app/pages/pricing/pricing-page.component.ts @@ -12,9 +12,10 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatTooltipModule } from '@angular/material/tooltip'; @@ -27,8 +28,8 @@ import { informationCircleOutline } from 'ionicons/icons'; import { StringValue } from 'ms'; -import { EMPTY, Subject } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; +import { catchError } from 'rxjs/operators'; @Component({ host: { class: 'page' }, @@ -46,7 +47,7 @@ import { catchError, takeUntil } from 'rxjs/operators'; styleUrls: ['./pricing-page.scss'], templateUrl: './pricing-page.html' }) -export class GfPricingPageComponent implements OnDestroy, OnInit { +export class GfPricingPageComponent implements OnInit { public baseCurrency: string; public coupon: number; public couponId: string; @@ -92,11 +93,10 @@ export class GfPricingPageComponent implements OnDestroy, OnInit { public routerLinkRegister = publicRoutes.register.routerLink; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private notificationService: NotificationService, private userService: UserService ) { @@ -124,7 +124,7 @@ export class GfPricingPageComponent implements OnDestroy, OnInit { this.price = subscriptionOffer?.price; this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -161,15 +161,10 @@ export class GfPricingPageComponent implements OnDestroy, OnInit { return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(({ sessionUrl }) => { window.location.href = sessionUrl; }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/public/public-page.component.ts b/apps/client/src/app/pages/public/public-page.component.ts index fe4b295db..9ccbbf52e 100644 --- a/apps/client/src/app/pages/public/public-page.component.ts +++ b/apps/client/src/app/pages/public/public-page.component.ts @@ -19,8 +19,10 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatTableDataSource } from '@angular/material/table'; @@ -29,8 +31,8 @@ import { AssetClass } from '@prisma/client'; import { StatusCodes } from 'http-status-codes'; import { isNumber } from 'lodash'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { EMPTY, Subject } from 'rxjs'; -import { catchError, takeUntil } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; +import { catchError } from 'rxjs/operators'; @Component({ host: { class: 'page' }, @@ -83,12 +85,12 @@ export class GfPublicPageComponent implements OnInit { public UNKNOWN_KEY = UNKNOWN_KEY; private accessId: string; - private unsubscribeSubject = new Subject(); public constructor( private activatedRoute: ActivatedRoute, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private router: Router ) { @@ -110,7 +112,7 @@ export class GfPublicPageComponent implements OnInit { this.dataService .fetchPublicPortfolio(this.accessId) .pipe( - takeUntil(this.unsubscribeSubject), + takeUntilDestroyed(this.destroyRef), catchError((error) => { if (error.status === StatusCodes.NOT_FOUND) { console.error(error); @@ -246,9 +248,4 @@ export class GfPublicPageComponent implements OnInit { }; } } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/public/public-page.html b/apps/client/src/app/pages/public/public-page.html index 079566e0d..9e5fd3748 100644 --- a/apps/client/src/app/pages/public/public-page.html +++ b/apps/client/src/app/pages/public/public-page.html @@ -73,12 +73,11 @@ @@ -116,7 +115,7 @@ @@ -135,7 +134,7 @@ @@ -155,7 +154,7 @@
    diff --git a/apps/client/src/app/pages/register/register-page.component.ts b/apps/client/src/app/pages/register/register-page.component.ts index 21b26944e..db199143f 100644 --- a/apps/client/src/app/pages/register/register-page.component.ts +++ b/apps/client/src/app/pages/register/register-page.component.ts @@ -1,4 +1,5 @@ import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; +import { UserService } from '@ghostfolio/client/services/user/user.service'; import { InfoItem, LineChartItem } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { GfLogoComponent } from '@ghostfolio/ui/logo'; @@ -7,15 +8,14 @@ import { DataService } from '@ghostfolio/ui/services'; import { Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { Router, RouterModule } from '@angular/router'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; import { UserAccountRegistrationDialogParams } from './user-account-registration-dialog/interfaces/interfaces'; import { GfUserAccountRegistrationDialogComponent } from './user-account-registration-dialog/user-account-registration-dialog.component'; @@ -28,7 +28,7 @@ import { GfUserAccountRegistrationDialogComponent } from './user-account-registr styleUrls: ['./register-page.scss'], templateUrl: './register-page.html' }) -export class GfRegisterPageComponent implements OnDestroy, OnInit { +export class GfRegisterPageComponent implements OnInit { public deviceType: string; public hasPermissionForAuthGoogle: boolean; public hasPermissionForAuthToken: boolean; @@ -37,18 +37,18 @@ export class GfRegisterPageComponent implements OnDestroy, OnInit { public historicalDataItems: LineChartItem[]; public info: InfoItem; - private unsubscribeSubject = new Subject(); - public constructor( private dataService: DataService, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private dialog: MatDialog, private router: Router, - private tokenStorageService: TokenStorageService + private tokenStorageService: TokenStorageService, + private userService: UserService ) { this.info = this.dataService.fetchInfo(); - this.tokenStorageService.signOut(); + this.userService.signOut(); } public ngOnInit() { @@ -93,7 +93,7 @@ export class GfRegisterPageComponent implements OnDestroy, OnInit { dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((authToken) => { if (authToken) { this.tokenStorageService.saveToken(authToken, true); @@ -102,9 +102,4 @@ export class GfRegisterPageComponent implements OnDestroy, OnInit { } }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts b/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts index a7707ad3b..cbbe2d29c 100644 --- a/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts +++ b/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts @@ -8,9 +8,11 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, + DestroyRef, Inject, ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; @@ -26,8 +28,6 @@ import { checkmarkOutline, copyOutline } from 'ionicons/icons'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; import { UserAccountRegistrationDialogParams } from './interfaces/interfaces'; @@ -63,12 +63,11 @@ export class GfUserAccountRegistrationDialogComponent { public routerLinkAboutTermsOfService = publicRoutes.about.subRoutes.termsOfService.routerLink; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, @Inject(MAT_DIALOG_DATA) public data: UserAccountRegistrationDialogParams, - private dataService: DataService + private dataService: DataService, + private destroyRef: DestroyRef ) { addIcons({ arrowForwardOutline, checkmarkOutline, copyOutline }); } @@ -76,7 +75,7 @@ export class GfUserAccountRegistrationDialogComponent { public createAccount() { this.dataService .postUser() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ accessToken, authToken, role }) => { this.accessToken = accessToken; this.authToken = authToken; diff --git a/apps/client/src/app/pages/resources/overview/resources-overview.component.html b/apps/client/src/app/pages/resources/overview/resources-overview.component.html index 3a6f18d40..86a334c79 100644 --- a/apps/client/src/app/pages/resources/overview/resources-overview.component.html +++ b/apps/client/src/app/pages/resources/overview/resources-overview.component.html @@ -7,7 +7,9 @@

    {{ item.title }}

    {{ item.description }}

    - Explore {{ item.title }} → + Explore {{ item.title }}
    }
    diff --git a/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.component.ts b/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.component.ts index c4cfd184e..bb4ae3889 100644 --- a/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.component.ts +++ b/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.component.ts @@ -1,13 +1,12 @@ import { personalFinanceTools } from '@ghostfolio/common/personal-finance-tools'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component, OnDestroy } from '@angular/core'; +import { Component } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { chevronForwardOutline } from 'ionicons/icons'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -16,7 +15,7 @@ import { Subject } from 'rxjs'; styleUrls: ['./personal-finance-tools-page.scss'], templateUrl: './personal-finance-tools-page.html' }) -export class PersonalFinanceToolsPageComponent implements OnDestroy { +export class PersonalFinanceToolsPageComponent { public pathAlternativeTo = publicRoutes.resources.subRoutes.personalFinanceTools.subRoutes.product .path + '-'; @@ -28,14 +27,7 @@ export class PersonalFinanceToolsPageComponent implements OnDestroy { }); public routerLinkAbout = publicRoutes.about.routerLink; - private unsubscribeSubject = new Subject(); - public constructor() { addIcons({ chevronForwardOutline }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/resources/resources-page.component.ts b/apps/client/src/app/pages/resources/resources-page.component.ts index 9db996f57..c25ef00d6 100644 --- a/apps/client/src/app/pages/resources/resources-page.component.ts +++ b/apps/client/src/app/pages/resources/resources-page.component.ts @@ -13,7 +13,6 @@ import { readerOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page has-tabs' }, @@ -47,8 +46,6 @@ export class ResourcesPageComponent implements OnInit { } ]; - private unsubscribeSubject = new Subject(); - public constructor(private deviceService: DeviceDetectorService) { addIcons({ bookOutline, libraryOutline, newspaperOutline, readerOutline }); } @@ -56,9 +53,4 @@ export class ResourcesPageComponent implements OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/user-account/user-account-page.component.ts b/apps/client/src/app/pages/user-account/user-account-page.component.ts index 7341660f2..24c150408 100644 --- a/apps/client/src/app/pages/user-account/user-account-page.component.ts +++ b/apps/client/src/app/pages/user-account/user-account-page.component.ts @@ -6,16 +6,16 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - OnDestroy, + DestroyRef, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatTabsModule } from '@angular/material/tabs'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { diamondOutline, keyOutline, settingsOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject, takeUntil } from 'rxjs'; @Component({ host: { class: 'page has-tabs' }, @@ -25,20 +25,19 @@ import { Subject, takeUntil } from 'rxjs'; styleUrls: ['./user-account-page.scss'], templateUrl: './user-account-page.html' }) -export class GfUserAccountPageComponent implements OnDestroy, OnInit { +export class GfUserAccountPageComponent implements OnInit { public deviceType: string; public tabs: TabConfiguration[] = []; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private userService: UserService ) { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -73,9 +72,4 @@ export class GfUserAccountPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/webauthn/webauthn-page.component.ts b/apps/client/src/app/pages/webauthn/webauthn-page.component.ts index 74631eeca..8e7e58fd1 100644 --- a/apps/client/src/app/pages/webauthn/webauthn-page.component.ts +++ b/apps/client/src/app/pages/webauthn/webauthn-page.component.ts @@ -2,12 +2,16 @@ import { TokenStorageService } from '@ghostfolio/client/services/token-storage.s import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; import { GfLogoComponent } from '@ghostfolio/ui/logo'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + DestroyRef, + OnInit +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { Router } from '@angular/router'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ host: { class: 'page' }, @@ -16,13 +20,12 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./webauthn-page.scss'], templateUrl: './webauthn-page.html' }) -export class GfWebauthnPageComponent implements OnDestroy, OnInit { +export class GfWebauthnPageComponent implements OnInit { public hasError = false; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private router: Router, private tokenStorageService: TokenStorageService, private webAuthnService: WebAuthnService @@ -35,7 +38,7 @@ export class GfWebauthnPageComponent implements OnDestroy, OnInit { public deregisterDevice() { this.webAuthnService .deregister() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.router.navigate(['/']); }); @@ -46,7 +49,7 @@ export class GfWebauthnPageComponent implements OnDestroy, OnInit { this.webAuthnService .login() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe( ({ authToken }) => { this.tokenStorageService.saveToken(authToken, false); @@ -59,9 +62,4 @@ export class GfWebauthnPageComponent implements OnDestroy, OnInit { } ); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/pages/zen/zen-page.component.ts b/apps/client/src/app/pages/zen/zen-page.component.ts index 280f33c25..633f15885 100644 --- a/apps/client/src/app/pages/zen/zen-page.component.ts +++ b/apps/client/src/app/pages/zen/zen-page.component.ts @@ -2,15 +2,19 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { TabConfiguration, User } from '@ghostfolio/common/interfaces'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; -import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { + ChangeDetectorRef, + Component, + DestroyRef, + OnInit +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatTabsModule } from '@angular/material/tabs'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { albumsOutline, analyticsOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; @Component({ host: { class: 'page has-tabs' }, @@ -19,20 +23,19 @@ import { takeUntil } from 'rxjs/operators'; styleUrls: ['./zen-page.scss'], templateUrl: './zen-page.html' }) -export class GfZenPageComponent implements OnDestroy, OnInit { +export class GfZenPageComponent implements OnInit { public deviceType: string; public tabs: TabConfiguration[] = []; public user: User; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private deviceService: DeviceDetectorService, private userService: UserService ) { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.tabs = [ @@ -59,9 +62,4 @@ export class GfZenPageComponent implements OnDestroy, OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/apps/client/src/app/services/import-activities.service.ts b/apps/client/src/app/services/import-activities.service.ts index 94d8470f7..55b5c44d5 100644 --- a/apps/client/src/app/services/import-activities.service.ts +++ b/apps/client/src/app/services/import-activities.service.ts @@ -97,9 +97,7 @@ export class ImportActivitiesService { isin: null, marketData: [], name: symbol, - scraperConfiguration: null, sectors: [], - symbolMapping: {}, url: null }); } diff --git a/apps/client/src/app/services/token-storage.service.ts b/apps/client/src/app/services/token-storage.service.ts index 5b9a29a08..f54aab828 100644 --- a/apps/client/src/app/services/token-storage.service.ts +++ b/apps/client/src/app/services/token-storage.service.ts @@ -1,19 +1,11 @@ -import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; - import { Injectable } from '@angular/core'; import { KEY_TOKEN } from './settings-storage.service'; -import { UserService } from './user/user.service'; @Injectable({ providedIn: 'root' }) export class TokenStorageService { - public constructor( - private userService: UserService, - private webAuthnService: WebAuthnService - ) {} - public getToken(): string { return ( window.sessionStorage.getItem(KEY_TOKEN) || @@ -25,23 +17,7 @@ export class TokenStorageService { if (staySignedIn) { window.localStorage.setItem(KEY_TOKEN, token); } - window.sessionStorage.setItem(KEY_TOKEN, token); - } - - public signOut() { - const utmSource = window.localStorage.getItem('utm_source'); - if (this.webAuthnService.isEnabled()) { - this.webAuthnService.deregister().subscribe(); - } - - window.localStorage.clear(); - window.sessionStorage.clear(); - - this.userService.remove(); - - if (utmSource) { - window.localStorage.setItem('utm_source', utmSource); - } + window.sessionStorage.setItem(KEY_TOKEN, token); } } diff --git a/apps/client/src/app/services/user/user.service.ts b/apps/client/src/app/services/user/user.service.ts index bd9d7d04c..44b63e056 100644 --- a/apps/client/src/app/services/user/user.service.ts +++ b/apps/client/src/app/services/user/user.service.ts @@ -1,3 +1,4 @@ +import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; import { Filter, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; @@ -26,7 +27,8 @@ export class UserService extends ObservableStore { public constructor( private deviceService: DeviceDetectorService, private dialog: MatDialog, - private http: HttpClient + private http: HttpClient, + private webAuthnService: WebAuthnService ) { super({ trackStateHistory: true }); @@ -93,10 +95,40 @@ export class UserService extends ObservableStore { return this.getFilters().length > 0; } - public remove() { + public reset() { this.setState({ user: null }, UserStoreActions.RemoveUser); } + public signOut() { + const utmSource = window.localStorage.getItem('utm_source'); + + if (this.webAuthnService.isEnabled()) { + this.webAuthnService.deregister().subscribe(); + } + + window.localStorage.clear(); + window.sessionStorage.clear(); + + void this.clearAllCookies(); + + this.reset(); + + if (utmSource) { + window.localStorage.setItem('utm_source', utmSource); + } + } + + private async clearAllCookies() { + if (!('cookieStore' in window)) { + console.warn('Cookie Store API not available in this browser'); + return; + } + + const cookies = await cookieStore.getAll(); + + await Promise.all(cookies.map(({ name }) => cookieStore.delete(name))); + } + private fetchUser(): Observable { return this.http.get('/api/v1/user').pipe( map((user) => { diff --git a/apps/client/src/assets/terms-of-service.md b/apps/client/src/assets/terms-of-service.md index d2a6e598d..4f62eee5b 100644 --- a/apps/client/src/assets/terms-of-service.md +++ b/apps/client/src/assets/terms-of-service.md @@ -51,8 +51,18 @@ This Agreement constitutes the entire agreement between the User and LICENSEE re LICENSEE reserves the right to modify this Agreement at any time. Users are encouraged to review this Agreement periodically for updates. Continued use of the Service after changes to this Agreement constitutes acceptance of the modified terms. +## Paid Plans + +LICENSEE offers paid plans (such as [Ghostfolio Premium](https://ghostfol.io/en/pricing)) for a fixed term as specified at the time of purchase. Paid plans do not renew automatically. + +LICENSEE may change the features or pricing of paid plans. For any existing paid plan, if a material feature is removed, LICENSEE may, acting in good faith, offer a pro-rata refund for the unused portion of the term upon the User’s request. + +## Refund Policy + +LICENSEE may offer a free trial of paid plans. Users are encouraged to use any available free trial to evaluate the Service before purchasing. While all purchases are final, a refund may be requested within fourteen (14) days of the purchase date by contacting LICENSEE. Requests after this period will not be honored. + By accessing or using the Service, or downloading data provided by the Service, the User acknowledges that they have read, understood, and agreed to be bound by this Terms of Service Agreement. For any questions or concerns regarding this Agreement, please contact us [here](https://ghostfol.io/en/about). -Date of Last Revision: March 29, 2025 +Date of Last Revision: April 6, 2026 diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index a6bec7df3..566d91cc7 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -38,7 +38,7 @@ apps/client/src/app/components/header/header.component.ts - 297 + 298 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -403,7 +403,7 @@ Balanç de Caixa apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 148 + 146 @@ -427,7 +427,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -439,7 +439,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -455,7 +455,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 139 + 135 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -499,7 +499,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -511,7 +511,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 145 + 141 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -531,15 +531,15 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 205 + 201 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 208 + 204 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 211 + 207 libs/ui/src/lib/account-balances/account-balances.component.html @@ -587,11 +587,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -619,11 +619,15 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 libs/ui/src/lib/account-balances/account-balances.component.html @@ -647,7 +651,7 @@ Realment vol suprimir aquest compte? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 150 + 148 @@ -655,7 +659,7 @@ Tipus apps/client/src/app/components/admin-jobs/admin-jobs.html - 48 + 57 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -675,7 +679,7 @@ Perfil d’Actiu apps/client/src/app/components/admin-jobs/admin-jobs.html - 52 + 61 @@ -683,11 +687,11 @@ Dades Històriques de Mercat apps/client/src/app/components/admin-jobs/admin-jobs.html - 54 + 63 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -695,7 +699,7 @@ Origen de les Dades apps/client/src/app/components/admin-jobs/admin-jobs.html - 82 + 91 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -707,7 +711,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 155 + 151 libs/ui/src/lib/i18n.ts @@ -719,7 +723,7 @@ Prioritat apps/client/src/app/components/admin-jobs/admin-jobs.html - 96 + 105 @@ -727,7 +731,7 @@ Intents apps/client/src/app/components/admin-jobs/admin-jobs.html - 120 + 129 @@ -735,7 +739,7 @@ Creat apps/client/src/app/components/admin-jobs/admin-jobs.html - 134 + 143 @@ -743,7 +747,7 @@ Finalitzat apps/client/src/app/components/admin-jobs/admin-jobs.html - 143 + 152 @@ -751,11 +755,11 @@ Estat apps/client/src/app/components/admin-jobs/admin-jobs.html - 152 + 161 apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -771,7 +775,7 @@ Aturar Processos apps/client/src/app/components/admin-jobs/admin-jobs.html - 193 + 202 @@ -779,7 +783,7 @@ Veure les Dades apps/client/src/app/components/admin-jobs/admin-jobs.html - 208 + 217 @@ -787,7 +791,7 @@ Veure Stacktrace apps/client/src/app/components/admin-jobs/admin-jobs.html - 216 + 225 @@ -795,7 +799,7 @@ Executar Procés apps/client/src/app/components/admin-jobs/admin-jobs.html - 220 + 229 @@ -803,7 +807,7 @@ Suprimir Procés apps/client/src/app/components/admin-jobs/admin-jobs.html - 224 + 233 @@ -819,7 +823,7 @@ Data apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 161 + 157 libs/ui/src/lib/account-balances/account-balances.component.html @@ -855,7 +859,7 @@ Punts de referència apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 126 + 125 @@ -863,11 +867,11 @@ Divises apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 131 + 130 apps/client/src/app/pages/public/public-page.html - 96 + 95 @@ -875,7 +879,7 @@ ETFs sense País apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 136 + 135 @@ -883,7 +887,7 @@ ETFs sense Sector apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 141 + 140 @@ -891,7 +895,7 @@ Filtra per... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 385 + 383 @@ -931,7 +935,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 44 + 40 @@ -999,7 +1003,7 @@ Oooh! No s’han pogut recopilar les dades históriques. libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts - 262 + 284 @@ -1007,7 +1011,7 @@ El preu de mercat actual és apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -1031,7 +1035,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 71 + 67 @@ -1063,7 +1067,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 53 + 72 @@ -1075,7 +1079,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1083,7 +1087,7 @@ apps/client/src/app/pages/public/public-page.html - 114 + 113 @@ -1095,7 +1099,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1107,7 +1111,7 @@ Mapatge de Símbols apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -1123,7 +1127,7 @@ Configuració del Proveïdor de Dades apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -1131,7 +1135,7 @@ Prova apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -1139,11 +1143,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1159,7 +1163,7 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -1167,7 +1171,7 @@ Notes apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1175,7 +1179,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 275 + 271 @@ -1219,7 +1223,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 124 + 120 @@ -1227,7 +1231,7 @@ Està segur qeu vol eliminar aquest cupó? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 193 + 196 @@ -1235,7 +1239,7 @@ Està segur que vol eliminar aquest missatge del sistema? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 206 + 209 @@ -1243,7 +1247,7 @@ Està segur que vol depurar el cache? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 230 + 233 @@ -1251,7 +1255,7 @@ Si us plau, afegeixi el seu missatge del sistema: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 250 + 253 @@ -1315,7 +1319,7 @@ Recollida de Dades apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -1387,7 +1391,15 @@ Està segur que vol eliminar aquesta plataforma? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 111 + + + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 @@ -1411,7 +1423,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 @@ -1427,7 +1439,7 @@ Plataformes apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -1435,7 +1447,7 @@ Etiquetes apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1459,7 +1471,7 @@ Està segur que vol eliminar aquesta etiqueta? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 108 @@ -1519,7 +1531,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 89 + 108 @@ -1551,11 +1563,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -1603,7 +1615,7 @@ Punt de Referència apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1679,15 +1691,15 @@ Oooh! El testimoni de seguretat és incorrecte. apps/client/src/app/components/header/header.component.ts - 312 + 313 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 154 + 152 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 193 + 191 @@ -1715,7 +1727,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 189 + 185 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1743,7 +1755,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 88 + 92 @@ -1767,7 +1779,7 @@ Informar d’un Problema amb les Dades apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 451 + 456 @@ -1775,7 +1787,7 @@ en Actiiu apps/client/src/app/components/home-holdings/home-holdings.component.ts - 64 + 63 @@ -1783,7 +1795,7 @@ Finalitzat apps/client/src/app/components/home-holdings/home-holdings.component.ts - 65 + 64 @@ -1807,7 +1819,7 @@ Gestionar Activitats apps/client/src/app/components/home-holdings/home-holdings.html - 67 + 64 @@ -1815,11 +1827,11 @@ Por apps/client/src/app/components/home-market/home-market.component.ts - 42 + 41 apps/client/src/app/components/markets/markets.component.ts - 47 + 46 libs/ui/src/lib/i18n.ts @@ -1831,11 +1843,11 @@ Cobdícia apps/client/src/app/components/home-market/home-market.component.ts - 43 + 42 apps/client/src/app/components/markets/markets.component.ts - 48 + 47 libs/ui/src/lib/i18n.ts @@ -1939,7 +1951,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 @@ -1959,7 +1971,7 @@ Import total apps/client/src/app/components/investment-chart/investment-chart.component.ts - 143 + 146 @@ -2095,7 +2107,7 @@ Rendiment brut absolut apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 73 + 77 @@ -2103,7 +2115,7 @@ Rendiment net absolut apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 107 + 111 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -2115,7 +2127,7 @@ Rendiment net apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 123 + 127 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -2127,7 +2139,7 @@ Actius totals apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 149 + 153 @@ -2135,7 +2147,7 @@ Actius apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 226 + 233 @@ -2143,7 +2155,7 @@ Poder adquisitiu apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 241 + 248 @@ -2151,7 +2163,7 @@ Exclòs de l’anàlisi apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 267 + 274 @@ -2159,7 +2171,7 @@ Passius apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 295 + 302 apps/client/src/app/pages/features/features-page.html @@ -2171,7 +2183,7 @@ Valor net apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 317 + 324 @@ -2179,7 +2191,7 @@ Rendiment anualitzat apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 331 + 338 @@ -2339,7 +2351,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 363 + 362 @@ -2347,11 +2359,11 @@ YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -2359,11 +2371,11 @@ 1 any apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -2371,11 +2383,11 @@ 5 anys apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -2391,11 +2403,11 @@ Màx apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 217 libs/ui/src/lib/assistant/assistant.component.ts - 415 + 414 @@ -2451,7 +2463,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 11 + 30 @@ -2535,7 +2547,7 @@ Automàtic apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 70 + 69 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2547,7 +2559,7 @@ De debò vols tancar el teu compte de Ghostfolio? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 205 @@ -2555,7 +2567,7 @@ De debò vols eliminar aquest mètode d’inici de sessió? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 279 @@ -2563,7 +2575,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -2571,7 +2583,7 @@ Ups! Hi ha hagut un error en configurar l’autenticació biomètrica. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 336 + 333 @@ -2619,7 +2631,7 @@ Localització apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2763,7 +2775,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 195 + 194 @@ -2783,7 +2795,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 193 + 192 @@ -2799,7 +2811,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 196 + 195 @@ -2935,11 +2947,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 375 + 381 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 66 + 85 apps/client/src/app/pages/accounts/accounts-page.html @@ -3039,7 +3051,7 @@ Dades de mercat apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 398 + 403 libs/common/src/lib/routes/routes.ts @@ -3073,6 +3085,10 @@ Overview Visió general + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 7 + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 114 @@ -3091,11 +3107,11 @@ apps/client/src/app/pages/admin/admin-page.component.ts - 48 + 45 apps/client/src/app/pages/resources/resources-page.component.ts - 30 + 29 libs/common/src/lib/routes/routes.ts @@ -3219,11 +3235,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -3239,7 +3255,7 @@ Com que ja has iniciat sessió, no pots accedir al compte de demostració. apps/client/src/app/pages/demo/demo-page.component.ts - 35 + 32 @@ -3267,7 +3283,7 @@ General apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -3275,7 +3291,7 @@ Núvol apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -3287,7 +3303,7 @@ Autoallotjament apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -3436,7 +3452,7 @@ apps/client/src/app/pages/public/public-page.html - 242 + 241 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -3488,7 +3504,7 @@ Mercats apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -3512,7 +3528,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 40 + 39 libs/common/src/lib/routes/routes.ts @@ -3960,7 +3976,7 @@ Job ID apps/client/src/app/components/admin-jobs/admin-jobs.html - 34 + 43 @@ -3996,7 +4012,7 @@ apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 115 + 113 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -4016,11 +4032,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 342 + 348 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 75 + 94 apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -4100,7 +4116,7 @@ Actualitzar el saldo d’efectiu apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 112 + 108 @@ -4108,7 +4124,7 @@ Preu unitari apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 214 + 210 libs/ui/src/lib/activities-table/activities-table.component.html @@ -4136,7 +4152,7 @@ Importar dividends apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 137 + 136 libs/ui/src/lib/activities-table/activities-table.component.html @@ -4152,7 +4168,7 @@ S’estan important dades... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 175 + 174 @@ -4160,7 +4176,7 @@ La importació s’ha completat apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 185 + 184 @@ -4176,7 +4192,7 @@ S’estan validant les dades... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 299 + 293 @@ -4348,7 +4364,7 @@ apps/client/src/app/pages/public/public-page.html - 151 + 150 @@ -4372,7 +4388,7 @@ apps/client/src/app/pages/public/public-page.html - 168 + 167 @@ -4380,7 +4396,7 @@ Latest activities apps/client/src/app/pages/public/public-page.html - 211 + 210 @@ -4392,7 +4408,7 @@ apps/client/src/app/pages/public/public-page.html - 177 + 176 @@ -4404,7 +4420,7 @@ apps/client/src/app/pages/public/public-page.html - 186 + 185 @@ -4480,7 +4496,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 365 + 372 apps/client/src/app/pages/features/features-page.html @@ -4488,11 +4504,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 202 + 198 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 76 + 75 libs/ui/src/lib/i18n.ts @@ -4512,11 +4528,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 81 + 80 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 97 + 96 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -4536,7 +4552,7 @@ Mensualment apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 91 + 90 @@ -4544,7 +4560,7 @@ Anualment apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 91 @@ -4552,7 +4568,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 447 @@ -4848,11 +4864,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -4876,7 +4892,7 @@ Continents apps/client/src/app/pages/public/public-page.html - 132 + 131 @@ -4884,7 +4900,7 @@ Vols refinar la teva estratègia d’inversió personal? apps/client/src/app/pages/public/public-page.html - 234 + 233 @@ -4900,7 +4916,7 @@ Ghostfolio us permet fer un seguiment de la vostra riquesa. apps/client/src/app/pages/public/public-page.html - 238 + 237 @@ -4930,6 +4946,10 @@ apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 88 + + libs/ui/src/lib/value/value.component.html + 18 + Personal Finance Tools @@ -5273,7 +5293,7 @@ Pertinença apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 48 + 67 libs/common/src/lib/routes/routes.ts @@ -5341,7 +5361,7 @@ Realment voleu eliminar el saldo d’aquest compte? libs/ui/src/lib/account-balances/account-balances.component.ts - 120 + 113 @@ -5405,7 +5425,7 @@ De veritat vols suprimir aquestes activitats? libs/ui/src/lib/activities-table/activities-table.component.ts - 278 + 282 @@ -5413,7 +5433,7 @@ Realment vols suprimir aquesta activitat? libs/ui/src/lib/activities-table/activities-table.component.ts - 288 + 292 @@ -5421,7 +5441,7 @@ Setmana fins avui libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5429,11 +5449,11 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5441,7 +5461,7 @@ Mes fins a la data libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5449,11 +5469,11 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5461,7 +5481,7 @@ Any fins a la data libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -5469,7 +5489,7 @@ any apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5481,7 +5501,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -5489,11 +5509,11 @@ anys apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -5501,7 +5521,7 @@ Perfils d’actius apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -5641,7 +5661,7 @@ Dipòsit libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 377 + 404 @@ -5653,11 +5673,11 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 352 + 359 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 387 + 414 libs/ui/src/lib/i18n.ts @@ -5669,7 +5689,7 @@ Estalvi libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 397 + 424 @@ -5697,7 +5717,7 @@ Mostra-ho tot libs/ui/src/lib/holdings-table/holdings-table.component.html - 216 + 212 @@ -5741,7 +5761,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5749,7 +5769,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 290 + 286 libs/ui/src/lib/i18n.ts @@ -5773,7 +5793,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5781,7 +5801,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 309 + 305 libs/ui/src/lib/i18n.ts @@ -5833,7 +5853,7 @@ Fons d’emergència apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 164 + 168 apps/client/src/app/pages/features/features-page.html @@ -5909,7 +5929,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 437 + 451 @@ -5920,6 +5940,14 @@ 27 + + No Activities + No Activities + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 145 + + Retirement Provision Provisió de jubilació @@ -5949,7 +5977,7 @@ Símbol apps/client/src/app/components/admin-jobs/admin-jobs.html - 68 + 77 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -5965,7 +5993,7 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 315 + 318 libs/ui/src/lib/i18n.ts @@ -6037,7 +6065,7 @@ Comissió apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 262 + 258 libs/ui/src/lib/activities-table/activities-table.component.html @@ -6081,7 +6109,7 @@ Efectiu apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 212 + 219 libs/ui/src/lib/i18n.ts @@ -6137,7 +6165,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 35 + 54 @@ -6281,7 +6309,7 @@ Vàlid fins a apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -6305,7 +6333,7 @@ apps/client/src/app/pages/public/public-page.html - 196 + 195 libs/ui/src/lib/benchmark/benchmark.component.html @@ -6313,11 +6341,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 439 + 453 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 452 + 467 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -6661,7 +6689,7 @@ Error apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6713,7 +6741,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6745,7 +6773,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 345 + 341 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -6753,7 +6781,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 46 + 47 libs/ui/src/lib/i18n.ts @@ -6765,7 +6793,7 @@ Close apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6791,13 +6819,17 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 68 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 127 + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 107 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 347 + 343 libs/ui/src/lib/i18n.ts @@ -6809,7 +6841,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 14 + 33 @@ -6833,7 +6865,7 @@ Portfolio Snapshot apps/client/src/app/components/admin-jobs/admin-jobs.html - 56 + 65 @@ -6900,6 +6932,14 @@ 42 + + has been copied to the clipboard + has been copied to the clipboard + + libs/ui/src/lib/value/value.component.ts + 180 + + From the beginning From the beginning @@ -7065,7 +7105,7 @@ Set API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7101,7 +7141,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 45 + 44 libs/common/src/lib/routes/routes.ts @@ -7117,7 +7157,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 34 + 33 libs/common/src/lib/routes/routes.ts @@ -7171,7 +7211,7 @@ of apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7179,7 +7219,7 @@ daily requests apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7187,7 +7227,7 @@ Remove API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7195,7 +7235,7 @@ Do you really want to delete the API key? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7215,7 +7255,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 98 + 117 @@ -7287,7 +7327,7 @@ Save apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7323,11 +7363,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 356 + 352 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 48 + 49 @@ -7339,7 +7379,7 @@ apps/client/src/app/components/user-account-access/user-account-access.component.ts - 260 + 257 @@ -7363,7 +7403,7 @@ Please enter your Ghostfolio API key. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7379,7 +7419,7 @@ AI prompt has been copied to the clipboard apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 201 + 199 @@ -7395,7 +7435,7 @@ Lazy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7403,7 +7443,7 @@ Instant apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7411,7 +7451,7 @@ Default Market Price apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7419,7 +7459,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7427,7 +7467,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7435,7 +7475,7 @@ HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7443,7 +7483,7 @@ end of day apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7451,7 +7491,7 @@ real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7459,7 +7499,7 @@ Open Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 202 + 200 @@ -7479,7 +7519,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 @@ -7491,7 +7531,7 @@ apps/client/src/app/components/home-overview/home-overview.component.ts - 55 + 54 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -7499,11 +7539,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 381 + 390 @@ -7607,11 +7647,11 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 235 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 170 + 167 @@ -7619,7 +7659,7 @@ Do you really want to generate a new security token for this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 241 + 240 @@ -7627,7 +7667,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 151 + 115 @@ -7692,7 +7732,7 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7700,7 +7740,7 @@ An error occurred while updating to (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7764,7 +7804,7 @@ someone apps/client/src/app/pages/public/public-page.component.ts - 59 + 61 @@ -7796,7 +7836,7 @@ Do you really want to delete this item? libs/ui/src/lib/benchmark/benchmark.component.ts - 144 + 139 @@ -7837,7 +7877,7 @@ Demo user account has been synced. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 274 + 277 @@ -7872,7 +7912,7 @@ 150 - + Fee Ratio Fee Ratio @@ -7880,17 +7920,17 @@ 152 - - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) + + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 - - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) + + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -8051,7 +8091,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 @@ -8059,11 +8099,11 @@ new apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts - 56 + 53 @@ -8220,7 +8260,7 @@ Do you really want to generate a new security token? apps/client/src/app/components/user-account-access/user-account-access.component.ts - 175 + 172 @@ -8244,7 +8284,7 @@ Stocks apps/client/src/app/components/markets/markets.component.ts - 52 + 51 apps/client/src/app/pages/features/features-page.html @@ -8256,7 +8296,7 @@ Cryptocurrencies apps/client/src/app/components/markets/markets.component.ts - 53 + 52 apps/client/src/app/pages/features/features-page.html @@ -8276,7 +8316,7 @@ Manage Asset Profile apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 466 + 471 @@ -8696,7 +8736,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 26 + 45 diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 44ee607c3..ff12aebcb 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -50,7 +50,7 @@ Typ apps/client/src/app/components/admin-jobs/admin-jobs.html - 48 + 57 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -106,7 +106,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -118,7 +118,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -134,7 +134,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 139 + 135 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -178,15 +178,15 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 205 + 201 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 208 + 204 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 211 + 207 libs/ui/src/lib/account-balances/account-balances.component.html @@ -234,11 +234,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -266,11 +266,15 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 libs/ui/src/lib/account-balances/account-balances.component.html @@ -294,7 +298,7 @@ Möchtest du dieses Konto wirklich löschen? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 150 + 148 @@ -302,7 +306,7 @@ Jobs löschen apps/client/src/app/components/admin-jobs/admin-jobs.html - 193 + 202 @@ -310,7 +314,7 @@ Datenquelle apps/client/src/app/components/admin-jobs/admin-jobs.html - 82 + 91 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -322,7 +326,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 155 + 151 libs/ui/src/lib/i18n.ts @@ -334,7 +338,7 @@ Versuche apps/client/src/app/components/admin-jobs/admin-jobs.html - 120 + 129 @@ -342,7 +346,7 @@ Erstellt apps/client/src/app/components/admin-jobs/admin-jobs.html - 134 + 143 @@ -350,7 +354,7 @@ Abgeschlossen apps/client/src/app/components/admin-jobs/admin-jobs.html - 143 + 152 @@ -358,11 +362,11 @@ Status apps/client/src/app/components/admin-jobs/admin-jobs.html - 152 + 161 apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -378,7 +382,7 @@ Anlageprofile apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -390,11 +394,11 @@ Historische Marktdaten apps/client/src/app/components/admin-jobs/admin-jobs.html - 54 + 63 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -402,7 +406,7 @@ Daten anzeigen apps/client/src/app/components/admin-jobs/admin-jobs.html - 208 + 217 @@ -410,7 +414,7 @@ Stacktrace anzeigen apps/client/src/app/components/admin-jobs/admin-jobs.html - 216 + 225 @@ -418,7 +422,7 @@ Job löschen apps/client/src/app/components/admin-jobs/admin-jobs.html - 224 + 233 @@ -434,7 +438,7 @@ Datum apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 161 + 157 libs/ui/src/lib/account-balances/account-balances.component.html @@ -502,7 +506,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 44 + 40 @@ -510,7 +514,7 @@ Möchtest du diesen Gutscheincode wirklich löschen? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 193 + 196 @@ -518,7 +522,7 @@ Möchtest du den Cache wirklich leeren? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 230 + 233 @@ -526,7 +530,7 @@ Bitte gebe deine Systemmeldung ein: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 250 + 253 @@ -682,7 +686,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 89 + 108 @@ -738,7 +742,7 @@ apps/client/src/app/pages/public/public-page.html - 242 + 241 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -754,7 +758,7 @@ apps/client/src/app/components/header/header.component.ts - 297 + 298 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -774,15 +778,15 @@ Ups! Falsches Sicherheits-Token. apps/client/src/app/components/header/header.component.ts - 312 + 313 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 154 + 152 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 193 + 191 @@ -790,7 +794,7 @@ Aktivitäten verwalten apps/client/src/app/components/home-holdings/home-holdings.html - 67 + 64 @@ -906,7 +910,7 @@ Absolute Brutto Performance apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 73 + 77 @@ -914,7 +918,7 @@ Absolute Netto Performance apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 107 + 111 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -926,7 +930,7 @@ Netto Performance apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 123 + 127 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -938,7 +942,7 @@ Gesamtanlagevermögen apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 149 + 153 @@ -946,7 +950,7 @@ Kaufkraft apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 241 + 248 @@ -954,7 +958,7 @@ Gesamtvermögen apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 317 + 324 @@ -962,7 +966,7 @@ Performance pro Jahr apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 331 + 338 @@ -982,7 +986,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -990,7 +994,7 @@ apps/client/src/app/pages/public/public-page.html - 114 + 113 @@ -1002,7 +1006,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1014,7 +1018,7 @@ Tags apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1030,7 +1034,7 @@ Datenfehler melden apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 451 + 456 @@ -1058,7 +1062,7 @@ Alle anzeigen libs/ui/src/lib/holdings-table/holdings-table.component.html - 216 + 212 @@ -1070,7 +1074,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 363 + 362 @@ -1078,11 +1082,11 @@ YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -1090,11 +1094,11 @@ 1J apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -1102,11 +1106,11 @@ 5J apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -1122,11 +1126,11 @@ Max apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 217 libs/ui/src/lib/assistant/assistant.component.ts - 415 + 414 @@ -1142,7 +1146,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 196 + 195 @@ -1234,7 +1238,7 @@ Möchtest du diese Anmeldemethode wirklich löschen? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 279 @@ -1294,7 +1298,7 @@ Lokalität apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1342,7 +1346,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 11 + 30 @@ -1390,11 +1394,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 375 + 381 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 66 + 85 apps/client/src/app/pages/accounts/accounts-page.html @@ -1434,7 +1438,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1446,7 +1450,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 145 + 141 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -1626,7 +1630,7 @@ Da du bereits eingeloggt bist, kannst du nicht auf die Live Demo zugreifen. apps/client/src/app/pages/demo/demo-page.component.ts - 35 + 32 @@ -1672,6 +1676,10 @@ Overview Übersicht + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 7 + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 114 @@ -1690,11 +1698,11 @@ apps/client/src/app/pages/admin/admin-page.component.ts - 48 + 45 apps/client/src/app/pages/resources/resources-page.component.ts - 30 + 29 libs/common/src/lib/routes/routes.ts @@ -1710,7 +1718,7 @@ Märkte apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -1734,7 +1742,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 40 + 39 libs/common/src/lib/routes/routes.ts @@ -1834,7 +1842,7 @@ apps/client/src/app/pages/public/public-page.html - 151 + 150 @@ -1930,7 +1938,7 @@ Aktuelle Woche apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 @@ -1974,7 +1982,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 124 + 120 @@ -1986,7 +1994,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 189 + 185 libs/ui/src/lib/activities-table/activities-table.component.html @@ -2002,7 +2010,7 @@ Stückpreis apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 214 + 210 libs/ui/src/lib/activities-table/activities-table.component.html @@ -2014,7 +2022,7 @@ Kommentar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2022,7 +2030,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 275 + 271 @@ -2034,7 +2042,7 @@ apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 115 + 113 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -2054,11 +2062,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 342 + 348 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 75 + 94 apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -2078,7 +2086,7 @@ Daten importieren... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 175 + 174 @@ -2086,7 +2094,7 @@ Der Import wurde abgeschlossen apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 185 + 184 @@ -2158,7 +2166,7 @@ Kontinente apps/client/src/app/pages/public/public-page.html - 132 + 131 @@ -2174,7 +2182,7 @@ Ghostfolio verschafft dir den Überblick über dein Vermögen. apps/client/src/app/pages/public/public-page.html - 238 + 237 @@ -2204,6 +2212,10 @@ apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 88 + + libs/ui/src/lib/value/value.component.html + 18 + Resources @@ -2322,7 +2334,7 @@ Möchtest du diese Aktivität wirklich löschen? libs/ui/src/lib/activities-table/activities-table.component.ts - 288 + 292 @@ -2394,7 +2406,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 195 + 194 @@ -2406,7 +2418,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 193 + 192 @@ -2454,7 +2466,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 53 + 72 @@ -2478,7 +2490,7 @@ apps/client/src/app/pages/public/public-page.html - 168 + 167 @@ -2486,7 +2498,7 @@ Neueste Aktivitäten apps/client/src/app/pages/public/public-page.html - 211 + 210 @@ -2498,7 +2510,7 @@ apps/client/src/app/pages/public/public-page.html - 177 + 176 @@ -2510,7 +2522,7 @@ apps/client/src/app/pages/public/public-page.html - 186 + 185 @@ -2526,7 +2538,7 @@ Monatlich apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 91 + 90 @@ -2542,7 +2554,7 @@ Einlage libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 377 + 404 @@ -2554,11 +2566,11 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 352 + 359 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 387 + 414 libs/ui/src/lib/i18n.ts @@ -2570,7 +2582,7 @@ Ersparnisse libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 397 + 424 @@ -2594,11 +2606,11 @@ Angst apps/client/src/app/components/home-market/home-market.component.ts - 42 + 41 apps/client/src/app/components/markets/markets.component.ts - 47 + 46 libs/ui/src/lib/i18n.ts @@ -2610,11 +2622,11 @@ Gier apps/client/src/app/components/home-market/home-market.component.ts - 43 + 42 apps/client/src/app/components/markets/markets.component.ts - 48 + 47 libs/ui/src/lib/i18n.ts @@ -2626,7 +2638,7 @@ Filtern nach... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 385 + 383 @@ -2662,11 +2674,11 @@ Das Formular konnte nicht validiert werden apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -2682,7 +2694,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2702,7 +2714,7 @@ Von der Analyse ausgenommen apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 267 + 274 @@ -2710,7 +2722,7 @@ Automatisch apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 70 + 69 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2746,7 +2758,7 @@ Gesamtbetrag apps/client/src/app/components/investment-chart/investment-chart.component.ts - 143 + 146 @@ -2798,7 +2810,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2806,7 +2818,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 290 + 286 libs/ui/src/lib/i18n.ts @@ -2822,7 +2834,7 @@ Symbol apps/client/src/app/components/admin-jobs/admin-jobs.html - 68 + 77 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -2838,7 +2850,7 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 315 + 318 libs/ui/src/lib/i18n.ts @@ -2862,7 +2874,7 @@ Bargeld apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 212 + 219 libs/ui/src/lib/i18n.ts @@ -2910,7 +2922,7 @@ Authentifizierung apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 35 + 54 @@ -2974,7 +2986,7 @@ Notfallfonds apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 164 + 168 apps/client/src/app/pages/features/features-page.html @@ -2994,7 +3006,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 437 + 451 @@ -3006,7 +3018,7 @@ apps/client/src/app/pages/public/public-page.html - 196 + 195 libs/ui/src/lib/benchmark/benchmark.component.html @@ -3014,11 +3026,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 439 + 453 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 452 + 467 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3122,7 +3134,7 @@ Symbol Zuordnung apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -3154,7 +3166,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 365 + 372 apps/client/src/app/pages/features/features-page.html @@ -3162,11 +3174,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 202 + 198 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 76 + 75 libs/ui/src/lib/i18n.ts @@ -3186,7 +3198,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3194,7 +3206,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 309 + 305 libs/ui/src/lib/i18n.ts @@ -3214,7 +3226,7 @@ Daten validieren... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 299 + 293 @@ -3230,7 +3242,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 71 + 67 @@ -3238,7 +3250,7 @@ Marktdaten apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 398 + 403 libs/common/src/lib/routes/routes.ts @@ -3290,7 +3302,7 @@ Jährlich apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 91 @@ -3298,7 +3310,7 @@ Dividenden importieren apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 137 + 136 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3314,7 +3326,7 @@ Gültig bis apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -3353,6 +3365,14 @@ 22 + + No Activities + Keine Aktivitäten + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 145 + + Retirement Provision Altersvorsorge @@ -3626,11 +3646,11 @@ Das Anlageprofil konnte nicht gespeichert werden apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -3650,7 +3670,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 88 + 92 @@ -3822,7 +3842,7 @@ Möchtest du diese Aktivitäten wirklich löschen? libs/ui/src/lib/activities-table/activities-table.component.ts - 278 + 282 @@ -3833,6 +3853,14 @@ 306 + + Explore + Entdecke + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 + + By Bis @@ -3854,7 +3882,7 @@ Aktuelles Jahr apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 @@ -3870,11 +3898,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3890,7 +3918,7 @@ Das Anlageprofil wurde gespeichert apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -3898,7 +3926,7 @@ Möchtest du diese Plattform wirklich löschen? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 111 @@ -3906,7 +3934,7 @@ Plattformen apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -3914,7 +3942,7 @@ Cash-Bestand aktualisieren apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 112 + 108 @@ -4094,7 +4122,7 @@ Verbindlichkeiten apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 295 + 302 apps/client/src/app/pages/features/features-page.html @@ -4250,7 +4278,7 @@ Scraper Konfiguration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -4494,7 +4522,7 @@ ETFs ohne Länder apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 136 + 135 @@ -4502,7 +4530,7 @@ ETFs ohne Sektoren apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 141 + 140 @@ -4510,7 +4538,7 @@ Anlagevermögen apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 226 + 233 @@ -4682,7 +4710,7 @@ Job ID apps/client/src/app/components/admin-jobs/admin-jobs.html - 34 + 43 @@ -4738,11 +4766,11 @@ Währungen apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 131 + 130 apps/client/src/app/pages/public/public-page.html - 96 + 95 @@ -4790,11 +4818,11 @@ Die Scraper Konfiguration konnte nicht geparsed werden apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -5456,7 +5484,7 @@ Gebühr apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 262 + 258 libs/ui/src/lib/activities-table/activities-table.component.html @@ -5488,7 +5516,7 @@ Möchtest du diesen Tag wirklich löschen? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 108 @@ -5556,7 +5584,7 @@ Mitgliedschaft apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 48 + 67 libs/common/src/lib/routes/routes.ts @@ -5588,7 +5616,7 @@ Anlageprofil apps/client/src/app/components/admin-jobs/admin-jobs.html - 52 + 61 @@ -5712,7 +5740,7 @@ Ups! Die historischen Daten konnten nicht geparsed werden. libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts - 262 + 284 @@ -5720,7 +5748,7 @@ Möchtest du diese Systemmeldung wirklich löschen? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 206 + 209 @@ -5744,7 +5772,7 @@ Cash-Bestände apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 148 + 146 @@ -5764,7 +5792,7 @@ Möchtest du diesen Cash-Bestand wirklich löschen? libs/ui/src/lib/account-balances/account-balances.component.ts - 120 + 113 @@ -5780,7 +5808,7 @@ Der aktuelle Marktpreis ist apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5788,7 +5816,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5876,11 +5904,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 81 + 80 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 97 + 96 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5900,7 +5928,7 @@ Position abschliessen apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 447 @@ -5940,7 +5968,7 @@ Seit Wochenbeginn libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5948,11 +5976,11 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5960,7 +5988,7 @@ Seit Monatsbeginn libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5968,11 +5996,11 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5980,7 +6008,7 @@ Seit Jahresbeginn libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -6016,7 +6044,7 @@ Jahr apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6028,7 +6056,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -6036,11 +6064,11 @@ Jahre apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -6056,7 +6084,7 @@ Finanzmarktdaten synchronisieren apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6068,7 +6096,7 @@ Allgemein apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6076,7 +6104,7 @@ Cloud apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6088,7 +6116,7 @@ Self-Hosting apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6129,7 +6157,7 @@ Aktiv apps/client/src/app/components/home-holdings/home-holdings.component.ts - 64 + 63 @@ -6137,7 +6165,7 @@ Abgeschlossen apps/client/src/app/components/home-holdings/home-holdings.component.ts - 65 + 64 @@ -6169,7 +6197,7 @@ Job ausführen apps/client/src/app/components/admin-jobs/admin-jobs.html - 220 + 229 @@ -6177,7 +6205,7 @@ Priorität apps/client/src/app/components/admin-jobs/admin-jobs.html - 96 + 105 @@ -6233,7 +6261,7 @@ Möchtest du dieses Ghostfolio Konto wirklich schliessen? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 205 @@ -6281,7 +6309,7 @@ Berücksichtigen in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6289,7 +6317,7 @@ Ups! Beim Einrichten der biometrischen Authentifizierung ist ein Fehler aufgetreten. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 336 + 333 @@ -6305,7 +6333,7 @@ Benchmarks apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 126 + 125 @@ -6353,7 +6381,7 @@ Möchtest du deine persönliche Anlagestrategie verfeinern? apps/client/src/app/pages/public/public-page.html - 234 + 233 @@ -6685,7 +6713,7 @@ Fehler apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6737,7 +6765,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6769,7 +6797,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 345 + 341 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -6777,7 +6805,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 46 + 47 libs/ui/src/lib/i18n.ts @@ -6789,7 +6817,7 @@ Schliessen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6815,13 +6843,17 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 68 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 127 + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 107 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 347 + 343 libs/ui/src/lib/i18n.ts @@ -6833,7 +6865,7 @@ Rolle apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 14 + 33 @@ -6857,7 +6889,7 @@ Portfolio Snapshot apps/client/src/app/components/admin-jobs/admin-jobs.html - 56 + 65 @@ -6924,6 +6956,14 @@ 42 + + has been copied to the clipboard + wurde in die Zwischenablage kopiert + + libs/ui/src/lib/value/value.component.ts + 180 + + From the beginning Seit Beginn @@ -7089,7 +7129,7 @@ API-Schlüssel setzen apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7125,7 +7165,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 45 + 44 libs/common/src/lib/routes/routes.ts @@ -7141,7 +7181,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 34 + 33 libs/common/src/lib/routes/routes.ts @@ -7195,7 +7235,7 @@ von apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7203,7 +7243,7 @@ täglichen Anfragen apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7211,7 +7251,7 @@ API-Schlüssel löschen apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7219,7 +7259,7 @@ Möchtest du den API-Schlüssel wirklich löschen? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7239,7 +7279,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 98 + 117 @@ -7311,7 +7351,7 @@ Speichern apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7347,11 +7387,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 356 + 352 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 48 + 49 @@ -7363,7 +7403,7 @@ apps/client/src/app/components/user-account-access/user-account-access.component.ts - 260 + 257 @@ -7387,7 +7427,7 @@ Bitte gebe deinen Ghostfolio API-Schlüssel ein. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7403,7 +7443,7 @@ KI-Anweisung wurde in die Zwischenablage kopiert apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 201 + 199 @@ -7419,7 +7459,7 @@ Verzögert apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7427,7 +7467,7 @@ Sofort apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7435,7 +7475,7 @@ Standardmarktpreis apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7443,7 +7483,7 @@ Modus apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7451,7 +7491,7 @@ Selektor apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7459,7 +7499,7 @@ HTTP Request-Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7467,7 +7507,7 @@ Tagesende apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7475,7 +7515,7 @@ in Echtzeit apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7483,7 +7523,7 @@ Öffne Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 202 + 200 @@ -7503,7 +7543,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 @@ -7515,7 +7555,7 @@ apps/client/src/app/components/home-overview/home-overview.component.ts - 55 + 54 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -7523,11 +7563,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 381 + 390 @@ -7631,11 +7671,11 @@ Sicherheits-Token apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 235 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 170 + 167 @@ -7643,7 +7683,7 @@ Möchtest du für diesen Benutzer wirklich ein neues Sicherheits-Token generieren? apps/client/src/app/components/admin-users/admin-users.component.ts - 241 + 240 @@ -7651,7 +7691,7 @@ Konto, Position oder Seite finden... libs/ui/src/lib/assistant/assistant.component.ts - 151 + 115 @@ -7716,7 +7756,7 @@ () wird bereits verwendet. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7724,7 +7764,7 @@ Bei der Änderung zu () ist ein Fehler aufgetreten. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7764,7 +7804,7 @@ jemand apps/client/src/app/pages/public/public-page.component.ts - 59 + 61 @@ -7796,7 +7836,7 @@ Möchtest du diesen Eintrag wirklich löschen? libs/ui/src/lib/benchmark/benchmark.component.ts - 144 + 139 @@ -7837,12 +7877,12 @@ Demo Benutzerkonto wurde synchronisiert. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 274 + 277 Sync Demo User Account - Synchronisiere Demo Benutzerkonto + Synchronisiere Demo Benutzerkonto apps/client/src/app/components/admin-overview/admin-overview.html 195 @@ -7872,7 +7912,7 @@ 150 - + Fee Ratio Gebührenverhältnis @@ -7880,17 +7920,17 @@ 152 - - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - Die Gebühren übersteigen ${thresholdMax}% deiner ursprünglichen Investition (${feeRatio}%) + + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + Die Gebühren übersteigen ${thresholdMax}% deines gesamten Investitionsvolumens (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 - - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - Die Gebühren übersteigen ${thresholdMax}% deiner ursprünglichen Investition (${feeRatio}%) nicht + + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + Die Gebühren übersteigen ${thresholdMax}% deines gesamten Investitionsvolumens (${feeRatio}%) nicht apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -8051,7 +8091,7 @@ Aktueller Monat apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 @@ -8059,11 +8099,11 @@ neu apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts - 56 + 53 @@ -8220,7 +8260,7 @@ Möchtest du wirklich ein neues Sicherheits-Token generieren? apps/client/src/app/components/user-account-access/user-account-access.component.ts - 175 + 172 @@ -8244,7 +8284,7 @@ Aktien apps/client/src/app/components/markets/markets.component.ts - 52 + 51 apps/client/src/app/pages/features/features-page.html @@ -8256,7 +8296,7 @@ Kryptowährungen apps/client/src/app/components/markets/markets.component.ts - 53 + 52 apps/client/src/app/pages/features/features-page.html @@ -8276,7 +8316,7 @@ Anlageprofil verwalten apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 466 + 471 @@ -8696,7 +8736,7 @@ Registrierungsdatum apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 26 + 45 diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index 7564e4d80..26c76ef6c 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -32,7 +32,7 @@ Grantee - Beneficiario + Usuario autorizado apps/client/src/app/components/access-table/access-table.component.html 11 @@ -51,7 +51,7 @@ Tipo apps/client/src/app/components/admin-jobs/admin-jobs.html - 48 + 57 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -76,7 +76,7 @@ Revoke - Revoca + Revocar apps/client/src/app/components/access-table/access-table.component.html 96 @@ -92,7 +92,7 @@ Do you really want to revoke this granted access? - ¿Quieres revocar el acceso concedido? + ¿Seguro que quieres revocar el acceso concedido? apps/client/src/app/components/access-table/access-table.component.ts 113 @@ -107,7 +107,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -119,7 +119,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -135,7 +135,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 139 + 135 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -179,15 +179,15 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 205 + 201 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 208 + 204 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 211 + 207 libs/ui/src/lib/account-balances/account-balances.component.html @@ -224,7 +224,7 @@ Edit - Edita + Editar apps/client/src/app/components/access-table/access-table.component.html 76 @@ -235,11 +235,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -252,7 +252,7 @@ Delete - Elimina + Eliminar apps/client/src/app/components/admin-market-data/admin-market-data.html 289 @@ -267,11 +267,15 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 libs/ui/src/lib/account-balances/account-balances.component.html @@ -292,18 +296,18 @@ Do you really want to delete this account? - ¿Estás seguro de eliminar esta cuenta? + ¿Seguro que quieres eliminar esta cuenta? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 150 + 148 Delete Jobs - Elimina los trabajos + Eliminar trabajos apps/client/src/app/components/admin-jobs/admin-jobs.html - 193 + 202 @@ -311,7 +315,7 @@ Fuente de datos apps/client/src/app/components/admin-jobs/admin-jobs.html - 82 + 91 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -323,7 +327,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 155 + 151 libs/ui/src/lib/i18n.ts @@ -335,7 +339,7 @@ Intentos apps/client/src/app/components/admin-jobs/admin-jobs.html - 120 + 129 @@ -343,7 +347,7 @@ Creado apps/client/src/app/components/admin-jobs/admin-jobs.html - 134 + 143 @@ -351,7 +355,7 @@ Finalizado apps/client/src/app/components/admin-jobs/admin-jobs.html - 143 + 152 @@ -359,16 +363,16 @@ Estado apps/client/src/app/components/admin-jobs/admin-jobs.html - 152 + 161 apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 and is driven by the efforts of its contributors - y es impulsado por los esfuerzos de sus contribuidores + y es impulsado por los esfuerzos de sus colaboradores apps/client/src/app/pages/about/overview/about-overview-page.html 49 @@ -376,10 +380,10 @@ Asset Profiles - Perfiles de activos. + Perfiles de activos apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -391,40 +395,40 @@ Datos históricos del mercado apps/client/src/app/components/admin-jobs/admin-jobs.html - 54 + 63 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 View Data - Visualiza los datos + Ver datos apps/client/src/app/components/admin-jobs/admin-jobs.html - 208 + 217 View Stacktrace - Visualiza Stacktrace + Ver Stacktrace apps/client/src/app/components/admin-jobs/admin-jobs.html - 216 + 225 Delete Job - Elimina el trabajo + Eliminar trabajo apps/client/src/app/components/admin-jobs/admin-jobs.html - 224 + 233 Details for - Detalles para + Detalles de libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html 2 @@ -435,7 +439,7 @@ Fecha apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 161 + 157 libs/ui/src/lib/account-balances/account-balances.component.html @@ -468,7 +472,7 @@ First Activity - Primera actividad + Primera operación apps/client/src/app/components/admin-market-data/admin-market-data.html 147 @@ -488,7 +492,7 @@ Activity Count - Recuento de actividad + Número de operaciones apps/client/src/app/components/admin-overview/admin-overview.html 19 @@ -503,23 +507,23 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 44 + 40 Do you really want to delete this coupon? - ¿Estás seguro de eliminar este cupón? + ¿Seguro que quieres eliminar este cupón? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 193 + 196 Do you really want to flush the cache? - ¿Estás seguro de limpiar la caché? + ¿Seguro que quieres limpiar la caché? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 230 + 233 @@ -527,7 +531,7 @@ Por favor, establece tu mensaje del sistema: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 250 + 253 @@ -540,7 +544,7 @@ per User - por usario + por usuario apps/client/src/app/components/admin-overview/admin-overview.html 28 @@ -548,7 +552,7 @@ Gather Profile Data - Recoger los datos del perfil + Recopilar datos del perfil apps/client/src/app/components/admin-market-data/admin-market-data.html 234 @@ -612,7 +616,7 @@ Housekeeping - Tareas domésticas + Limpieza del sistema apps/client/src/app/components/admin-overview/admin-overview.html 184 @@ -628,7 +632,7 @@ Do you really want to delete this user? - ¿Estás seguro de eliminar este usuario? + ¿Seguro que quieres eliminar este usuario? apps/client/src/app/components/admin-users/admin-users.component.ts 215 @@ -652,7 +656,7 @@ No auto-renewal on membership. - No se renueva automáticamente la membresía. + Sin renovación automática de la membresía. apps/client/src/app/components/user-account-membership/user-account-membership.html 74 @@ -660,14 +664,14 @@ Engagement per Day - Contratación diaria + Interacción diaria apps/client/src/app/components/admin-users/admin-users.html 140 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 89 + 108 @@ -680,7 +684,7 @@ Current Market Mood - Estado de ánimo del mercado + Sentimiento del mercado apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.html 12 @@ -723,7 +727,7 @@ apps/client/src/app/pages/public/public-page.html - 242 + 241 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -739,7 +743,7 @@ apps/client/src/app/components/header/header.component.ts - 297 + 298 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -756,26 +760,26 @@ Oops! Incorrect Security Token. - Vaya! Token de seguridad incorrecto. + ¡Vaya! Token de seguridad incorrecto. apps/client/src/app/components/header/header.component.ts - 312 + 313 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 154 + 152 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 193 + 191 Manage Activities - Gestión de las operaciones + Gestionar operaciones apps/client/src/app/components/home-holdings/home-holdings.html - 67 + 64 @@ -891,7 +895,7 @@ Rendimiento bruto absoluto apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 73 + 77 @@ -899,7 +903,7 @@ Rendimiento neto absoluto apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 107 + 111 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -911,7 +915,7 @@ Rendimiento neto apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 123 + 127 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -923,15 +927,15 @@ Total de activos apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 149 + 153 Buying Power - Capacidad de compra + Poder adquisitivo apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 241 + 248 @@ -939,7 +943,7 @@ Patrimonio neto apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 317 + 324 @@ -947,12 +951,12 @@ Rendimiento anualizado apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 331 + 338 Please set the amount of your emergency fund. - Por favor, ingresa la cantidad de tu fondo de emergencia: + Por favor, ingresa la cantidad de tu fondo de emergencia. apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts 111 @@ -967,7 +971,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -975,7 +979,7 @@ apps/client/src/app/pages/public/public-page.html - 114 + 113 @@ -987,7 +991,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -999,7 +1003,7 @@ Etiquetas apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1012,10 +1016,10 @@ Report Data Glitch - Reporta un anomalía de los datos + Reportar anomalía en los datos apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 451 + 456 @@ -1043,7 +1047,7 @@ Mostrar todos libs/ui/src/lib/holdings-table/holdings-table.component.html - 216 + 212 @@ -1055,19 +1059,19 @@ libs/ui/src/lib/assistant/assistant.component.ts - 363 + 362 YTD - Desde principio de año + YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -1075,11 +1079,11 @@ 1 año apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -1087,16 +1091,16 @@ 5 años apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 Performance with currency effect - Rendimiento con el efecto del tipo de cambio de divisa + Rendimiento con el efecto de la divisa apps/client/src/app/pages/portfolio/analysis/analysis-page.html 135 @@ -1107,11 +1111,11 @@ Máximo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 217 libs/ui/src/lib/assistant/assistant.component.ts - 415 + 414 @@ -1127,12 +1131,12 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 196 + 195 About - Sobre + Acerca de apps/client/src/app/components/footer/footer.component.html 20 @@ -1184,7 +1188,7 @@ Please enter your coupon code. - Por favor, ingresa tu código de cupón: + Por favor, ingresa tu código de cupón. apps/client/src/app/components/user-account-membership/user-account-membership.component.ts 210 @@ -1192,7 +1196,7 @@ Could not redeem coupon code - No se puede canjear este código de cupón + No se pudo canjear el código de cupón apps/client/src/app/components/user-account-membership/user-account-membership.component.ts 174 @@ -1200,7 +1204,7 @@ Coupon code has been redeemed - El codigo de cupón ha sido canjeado + El código del cupón ha sido canjeado apps/client/src/app/components/user-account-membership/user-account-membership.component.ts 187 @@ -1208,7 +1212,7 @@ Reload - Refrescar + Recargar apps/client/src/app/components/user-account-membership/user-account-membership.component.ts 188 @@ -1216,10 +1220,10 @@ Do you really want to remove this sign in method? - ¿Estás seguro de eliminar este método de acceso? + ¿Seguro que quieres eliminar este método de acceso? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 279 @@ -1244,7 +1248,7 @@ Try Premium - Prueba Premium + Probar Premium apps/client/src/app/components/user-account-membership/user-account-membership.html 53 @@ -1252,7 +1256,7 @@ Redeem Coupon - Canjea el cupón + Canjear cupón apps/client/src/app/components/user-account-membership/user-account-membership.html 67 @@ -1276,10 +1280,10 @@ Locale - Ubicación + Configuración regional apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1316,7 +1320,7 @@ User ID - ID usuario + ID de usuario apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 51 @@ -1327,7 +1331,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 11 + 30 @@ -1375,11 +1379,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 375 + 381 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 66 + 85 apps/client/src/app/pages/accounts/accounts-page.html @@ -1396,7 +1400,7 @@ Update account - Editar cuenta + Actualizar cuenta apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 8 @@ -1412,14 +1416,14 @@ Currency - Divisa base + Divisa apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 199 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1431,7 +1435,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 145 + 141 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -1476,7 +1480,7 @@ Account ID - ID cuenta + ID de cuenta apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 96 @@ -1611,12 +1615,12 @@ Como estás conectado, no puedes acceder a la cuenta de demostración. apps/client/src/app/pages/demo/demo-page.component.ts - 35 + 32 Frequently Asked Questions (FAQ) - Preguntas más frecuentes (FAQ) + Preguntas frecuentes (FAQ) apps/client/src/app/components/footer/footer.component.html 33 @@ -1657,6 +1661,10 @@ Overview Visión general + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 7 + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 114 @@ -1675,11 +1683,11 @@ apps/client/src/app/pages/admin/admin-page.component.ts - 48 + 45 apps/client/src/app/pages/resources/resources-page.component.ts - 30 + 29 libs/common/src/lib/routes/routes.ts @@ -1695,7 +1703,7 @@ Mercados apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -1719,7 +1727,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 40 + 39 libs/common/src/lib/routes/routes.ts @@ -1780,7 +1788,7 @@ By Holding - Por participación + Por posiciones apps/client/src/app/pages/portfolio/allocations/allocations-page.html 107 @@ -1819,7 +1827,7 @@ apps/client/src/app/pages/public/public-page.html - 151 + 150 @@ -1844,7 +1852,7 @@ Top - Lo mejor + Mejores apps/client/src/app/pages/portfolio/analysis/analysis-page.html 305 @@ -1852,7 +1860,7 @@ Bottom - Lo peor + Peores apps/client/src/app/pages/portfolio/analysis/analysis-page.html 354 @@ -1876,7 +1884,7 @@ Holdings - Participaciones + Posiciones apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html 102 @@ -1904,7 +1912,7 @@ Update activity - Actualizar opereación + Actualizar operación apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 10 @@ -1915,7 +1923,7 @@ Semana actual apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 @@ -1959,7 +1967,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 124 + 120 @@ -1971,7 +1979,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 189 + 185 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1987,7 +1995,7 @@ Precio unitario apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 214 + 210 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1999,7 +2007,7 @@ Nota apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2007,19 +2015,19 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 275 + 271 Activities - Operación + Operaciones apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html 86 apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 115 + 113 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -2039,11 +2047,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 342 + 348 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 75 + 94 apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -2063,7 +2071,7 @@ Importando datos... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 175 + 174 @@ -2071,12 +2079,12 @@ La importación se ha completado apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 185 + 184 or start a discussion at - o iniciar una discusión en + o inicia una discusión en apps/client/src/app/pages/about/overview/about-overview-page.html 94 @@ -2143,12 +2151,12 @@ Continentes apps/client/src/app/pages/public/public-page.html - 132 + 131 Sustainable retirement income - Ingreso sostenible de retiro + Ingresos sostenibles para la jubilación apps/client/src/app/pages/portfolio/fire/fire-page.html 41 @@ -2156,10 +2164,10 @@ Ghostfolio empowers you to keep track of your wealth. - Ghostfolio te permite hacer un seguimiento de tu riqueza. + Ghostfolio te permite hacer un seguimiento de tu patrimonio. apps/client/src/app/pages/public/public-page.html - 238 + 237 @@ -2189,6 +2197,10 @@ apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 88 + + libs/ui/src/lib/value/value.component.html + 18 + Resources @@ -2304,10 +2316,10 @@ Do you really want to delete this activity? - ¿Estás seguro de eliminar esta operación? + ¿Seguro que quieres eliminar esta operación? libs/ui/src/lib/activities-table/activities-table.component.ts - 288 + 292 @@ -2320,7 +2332,7 @@ contact us - contactarnos + contáctanos apps/client/src/app/pages/pricing/pricing-page.html 336 @@ -2336,7 +2348,7 @@ Annual Interest Rate - Tipo de interés anual + Tasa de interés anual libs/ui/src/lib/fire-calculator/fire-calculator.component.html 21 @@ -2368,14 +2380,14 @@ Oops! Something went wrong. - Vaya! Algo no funcionó bien. + ¡Vaya! Algo no funcionó bien. apps/client/src/app/core/http-response.interceptor.ts 86 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 193 + 192 @@ -2391,7 +2403,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 195 + 194 @@ -2415,15 +2427,15 @@ apps/client/src/app/pages/public/public-page.html - 168 + 167 Latest activities - Últimas actividades + Últimas operaciones apps/client/src/app/pages/public/public-page.html - 211 + 210 @@ -2443,7 +2455,7 @@ apps/client/src/app/pages/public/public-page.html - 186 + 185 @@ -2455,7 +2467,7 @@ apps/client/src/app/pages/public/public-page.html - 177 + 176 @@ -2487,7 +2499,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 53 + 72 @@ -2511,7 +2523,7 @@ Ahorros libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 397 + 424 @@ -2523,11 +2535,11 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 352 + 359 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 387 + 414 libs/ui/src/lib/i18n.ts @@ -2547,7 +2559,7 @@ Depósito libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 377 + 404 @@ -2555,7 +2567,7 @@ Mensual apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 91 + 90 @@ -2579,11 +2591,11 @@ Miedo apps/client/src/app/components/home-market/home-market.component.ts - 42 + 41 apps/client/src/app/components/markets/markets.component.ts - 47 + 46 libs/ui/src/lib/i18n.ts @@ -2595,11 +2607,11 @@ Codicia apps/client/src/app/components/home-market/home-market.component.ts - 43 + 42 apps/client/src/app/components/markets/markets.component.ts - 48 + 47 libs/ui/src/lib/i18n.ts @@ -2611,12 +2623,12 @@ Filtrar por... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 385 + 383 Hello, has shared a Portfolio with you! - Hola, ha compartido una Cartera contigo! + ¡Hola, ha compartido una cartera contigo! apps/client/src/app/pages/public/public-page.html 5 @@ -2644,10 +2656,10 @@ Benchmark - Benchmark + Índice de referencia apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2659,11 +2671,11 @@ No se pudo validar el formulario apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -2687,7 +2699,7 @@ Excluido del análisis apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 267 + 274 @@ -2695,7 +2707,7 @@ Automático apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 70 + 69 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2731,12 +2743,12 @@ Importe total apps/client/src/app/components/investment-chart/investment-chart.component.ts - 143 + 146 Portfolio Evolution - Evolución cartera + Evolución de la cartera apps/client/src/app/pages/portfolio/analysis/analysis-page.html 407 @@ -2783,7 +2795,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2791,7 +2803,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 290 + 286 libs/ui/src/lib/i18n.ts @@ -2807,7 +2819,7 @@ Símbolo apps/client/src/app/components/admin-jobs/admin-jobs.html - 68 + 77 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -2823,7 +2835,7 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 315 + 318 libs/ui/src/lib/i18n.ts @@ -2847,7 +2859,7 @@ Efectivo apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 212 + 219 libs/ui/src/lib/i18n.ts @@ -2856,7 +2868,7 @@ Commodity - Bien + Materia prima libs/ui/src/lib/i18n.ts 47 @@ -2864,7 +2876,7 @@ Equity - Capital + Renta variable apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html 57 @@ -2895,7 +2907,7 @@ Autenticación apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 35 + 54 @@ -2959,7 +2971,7 @@ Fondo de emergencia apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 164 + 168 apps/client/src/app/pages/features/features-page.html @@ -2979,7 +2991,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 437 + 451 @@ -2991,7 +3003,7 @@ apps/client/src/app/pages/public/public-page.html - 196 + 195 libs/ui/src/lib/benchmark/benchmark.component.html @@ -2999,11 +3011,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 439 + 453 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 452 + 467 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3044,7 +3056,7 @@ If you retire today, you would be able to withdraw - Si te retirases hoy, podrías sacar + Si te jubilas hoy, podrías retirar apps/client/src/app/pages/portfolio/fire/fire-page.html 68 @@ -3068,7 +3080,7 @@ The following file formats are supported: - Los siguientes formatos de archivo están soportados: + Los siguientes formatos de archivo son compatibles: apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html 90 @@ -3088,7 +3100,7 @@ Activities Count - Recuento de actividades + Número de operaciones apps/client/src/app/components/admin-market-data/admin-market-data.html 156 @@ -3096,7 +3108,7 @@ Refresh - Refrescar + Actualizar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 21 @@ -3107,12 +3119,12 @@ Mapeo de símbolos apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 Looking for a student discount? - ¿Buscando un descuento para estudiantes? + ¿Buscas un descuento para estudiantes? apps/client/src/app/pages/pricing/pricing-page.html 342 @@ -3131,7 +3143,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 365 + 372 apps/client/src/app/pages/features/features-page.html @@ -3139,11 +3151,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 202 + 198 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 76 + 75 libs/ui/src/lib/i18n.ts @@ -3171,7 +3183,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3179,7 +3191,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 309 + 305 libs/ui/src/lib/i18n.ts @@ -3199,7 +3211,7 @@ Validando datos... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 299 + 293 @@ -3215,7 +3227,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 71 + 67 @@ -3223,7 +3235,7 @@ Datos del mercado apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 398 + 403 libs/common/src/lib/routes/routes.ts @@ -3252,7 +3264,7 @@ Holding - Participación + Posición apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html 32 @@ -3275,15 +3287,15 @@ Anual apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 91 Import Dividends - Importar Dividendos + Importar dividendos apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 137 + 136 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3299,7 +3311,7 @@ Válido hasta apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -3324,7 +3336,7 @@ Higher Risk - Riesgo mayor + Mayor riesgo libs/ui/src/lib/i18n.ts 20 @@ -3338,6 +3350,14 @@ 22 + + No Activities + Sin operaciones + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 145 + + Retirement Provision Provisión de jubilación @@ -3348,7 +3368,7 @@ Everything in Basic, plus - Todo en Básico, más + Todo lo incluido en Basic, más apps/client/src/app/pages/pricing/pricing-page.html 199 @@ -3364,7 +3384,7 @@ Protection for sensitive information like absolute performances and quantity values - Protección de información confidencial como rendimientos absolutos y valores cuantitativos + Protección de información confidencial como rendimientos absolutos y cantidades apps/client/src/app/components/user-account-settings/user-account-settings.html 194 @@ -3388,7 +3408,7 @@ Are you an ambitious investor who needs the full picture? - ¿Es usted un inversor ambicioso que necesita una visión completa? + ¿Eres un inversor ambicioso que necesita una visión completa? apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 15 @@ -3412,7 +3432,7 @@ Performance Benchmarks - Puntos de referencia de rendimiento + Índices de referencia de rendimiento apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 32 @@ -3444,7 +3464,7 @@ and more Features... - y más características... + y más funcionalidades... apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 44 @@ -3460,7 +3480,7 @@ Skip - Saltar + Omitir apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 59 @@ -3516,7 +3536,7 @@ Unlimited Transactions - Transacciones ilimitadas + Operaciones ilimitadas apps/client/src/app/pages/pricing/pricing-page.html 35 @@ -3540,7 +3560,7 @@ Portfolio Performance - Rendimiento del Portfolio + Rendimiento de la cartera apps/client/src/app/pages/pricing/pricing-page.html 43 @@ -3552,7 +3572,7 @@ Self-hosted, update manually. - Auto alojado, actualiza manualmente. + Autoalojado, actualiza manualmente. apps/client/src/app/pages/pricing/pricing-page.html 84 @@ -3572,7 +3592,7 @@ For new investors who are just getting started with trading. - Para nuevos inversores que estan empezando con el trading. + Para nuevos inversores que están empezando con el trading. apps/client/src/app/pages/pricing/pricing-page.html 119 @@ -3580,7 +3600,7 @@ Fully managed Ghostfolio cloud offering. - Oferta en la nube de Ghostfolio totalmente administrada. + Oferta de Ghostfolio en la nube totalmente gestionada. apps/client/src/app/pages/pricing/pricing-page.html 150 @@ -3592,7 +3612,7 @@ For ambitious investors who need the full picture of their financial assets. - Para inversores ambiciosos que necesitan una visión completa de sus activos financieros + Para inversores ambiciosos que necesitan una visión completa de sus activos financieros. apps/client/src/app/pages/pricing/pricing-page.html 193 @@ -3611,11 +3631,11 @@ No se pudo guardar el perfil del activo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -3635,12 +3655,12 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 88 + 92 Portfolio Allocations - Distribucion del Portfolio + Distribución de la cartera apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 28 @@ -3668,7 +3688,7 @@ Data Import and Export - Importacion y exportacion de datos + Importación y exportación de datos apps/client/src/app/pages/pricing/pricing-page.html 63 @@ -3680,7 +3700,7 @@ Switch to Ghostfolio Premium easily - Cambia a Ghostfolio Premium facilmente + Cambia a Ghostfolio Premium fácilmente libs/ui/src/lib/i18n.ts 13 @@ -3696,7 +3716,7 @@ Email and Chat Support - Soporte a Traves de Email y Chat + Soporte a través de correo electrónico y chat apps/client/src/app/pages/pricing/pricing-page.html 248 @@ -3704,7 +3724,7 @@ Switch to Ghostfolio Premium or Ghostfolio Open Source easily - Cambie a Ghostfolio Premium o Ghostfolio Open Source fácilmente + Cambia a Ghostfolio Premium o Ghostfolio Open Source fácilmente libs/ui/src/lib/i18n.ts 12 @@ -3712,7 +3732,7 @@ Switch to Ghostfolio Open Source or Ghostfolio Basic easily - Cambie a Ghostfolio Open Source o Ghostfolio Basic fácilmente + Cambia a Ghostfolio Open Source o Ghostfolio Basic fácilmente libs/ui/src/lib/i18n.ts 14 @@ -3736,7 +3756,7 @@ Professional Data Provider - Proveedor de datos profesional + Proveedor de datos profesional apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 40 @@ -3796,10 +3816,10 @@ Do you really want to delete these activities? - ¿Realmente deseas eliminar estas actividades? + ¿Seguro que quieres eliminar estas operaciones? libs/ui/src/lib/activities-table/activities-table.component.ts - 278 + 282 @@ -3810,6 +3830,14 @@ 306 + + Explore + Explorar + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 + + By Por @@ -3831,12 +3859,12 @@ Año actual apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 Add platform - Agregar plataforma + Añadir plataforma apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.html 10 @@ -3844,14 +3872,14 @@ Url - ¿La URL? + Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3864,18 +3892,18 @@ Asset profile has been saved - El perfil del activo ha sido guardado + Perfil del activo guardado apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 Do you really want to delete this platform? - ¿Realmente deseas eliminar esta plataforma? + ¿Seguro que quieres eliminar esta plataforma? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 111 @@ -3883,7 +3911,7 @@ Plataformas apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -3891,7 +3919,7 @@ Actualizar saldo en efectivo apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 112 + 108 @@ -3904,7 +3932,7 @@ Upgrade to Ghostfolio Premium today and gain access to exclusive features to enhance your investment experience: - Actualiza a Ghostfolio Premium hoy y accede a características exclusivas para mejorar tu experiencia de inversión: + Actualiza a Ghostfolio Premium hoy y accede a funcionalidades exclusivas para mejorar tu experiencia de inversión: apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 18 @@ -3920,7 +3948,7 @@ Add Platform - Agregar plataforma + Añadir plataforma apps/client/src/app/components/admin-platform/admin-platform.component.html 9 @@ -3928,7 +3956,7 @@ Settings - Configuraciones + Ajustes apps/client/src/app/components/user-account-settings/user-account-settings.html 2 @@ -3944,7 +3972,7 @@ This activity already exists. - Esta actividad ya existe. + Esta operación ya existe. libs/ui/src/lib/i18n.ts 21 @@ -3952,7 +3980,7 @@ Manage Benchmarks - Gestionar puntos de referencia + Gestionar índices de referencia apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html 35 @@ -3984,7 +4012,7 @@ Select Activities - Seleccionar dividendos + Seleccionar operaciones apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html 115 @@ -4071,7 +4099,7 @@ Pasivos apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 295 + 302 apps/client/src/app/pages/features/features-page.html @@ -4144,7 +4172,7 @@ Multi-Accounts - Cuentas múltiples + Múltiples cuentas apps/client/src/app/pages/features/features-page.html 127 @@ -4152,7 +4180,7 @@ Portfolio Calculations - Cálculos de portafolio + Cálculos de la cartera apps/client/src/app/pages/features/features-page.html 141 @@ -4168,7 +4196,7 @@ Market Mood - Modo de mercado + Sentimiento del mercado apps/client/src/app/pages/features/features-page.html 215 @@ -4184,7 +4212,7 @@ Multi-Language - Multilenguaje + Multilingüe apps/client/src/app/pages/features/features-page.html 259 @@ -4208,7 +4236,7 @@ Liability - Responsabilidad + Pasivo libs/ui/src/lib/i18n.ts 41 @@ -4216,7 +4244,7 @@ and we share aggregated key metrics of the platform’s performance - y compartimos agregados métricas clave del rendimiento de la plataforma + y compartimos métricas clave agregadas del rendimiento de la plataforma apps/client/src/app/pages/about/overview/about-overview-page.html 32 @@ -4227,12 +4255,12 @@ Configuración del scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 Add Asset Profile - Agregar perfil de activo + Añadir perfil de activo apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html 7 @@ -4252,7 +4280,7 @@ Discover Open Source Alternatives for Personal Finance Tools - Descubra alternativas de software libre para herramientas de finanzas personales + Descubre alternativas de software de código abierto para herramientas de finanzas personales apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.html 5 @@ -4404,7 +4432,7 @@ Effortlessly track, analyze, and visualize your wealth with Ghostfolio. - Siga, analice y visualice su patrimonio sin esfuerzo con Ghostfolio. + Sigue, analiza y visualiza tu patrimonio sin esfuerzo con Ghostfolio. apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 329 @@ -4448,7 +4476,7 @@ Buy - Comprar + Compra apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html 31 @@ -4460,7 +4488,7 @@ Valuable - Valioso + Activo de valor libs/ui/src/lib/i18n.ts 43 @@ -4471,7 +4499,7 @@ ETFs sin países apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 136 + 135 @@ -4479,7 +4507,7 @@ ETFs sin sectores apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 141 + 140 @@ -4487,7 +4515,7 @@ Activos apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 226 + 233 @@ -4532,7 +4560,7 @@ The source code is fully available as open source software (OSS) under the AGPL-3.0 license - El código fuente está disponible completamente en software de código abierto (OSS) bajo la licencia AGPL-3.0 + El código fuente está disponible como software de código abierto (OSS) bajo la licencia AGPL-3.0 apps/client/src/app/pages/about/overview/about-overview-page.html 16 @@ -4556,7 +4584,7 @@ Capture your activities - Captura tus actividades + Captura tus operaciones apps/client/src/app/components/home-overview/home-overview.html 28 @@ -4564,7 +4592,7 @@ Record your investment activities to keep your portfolio up to date. - Registra tus actividades de inversión para mantener tu portafolio actualizado. + Registra tus operaciones de inversión para mantener tu cartera actualizada. apps/client/src/app/components/home-overview/home-overview.html 30 @@ -4572,7 +4600,7 @@ Monitor and analyze your portfolio - Monitorea y analiza tu portafolio + Monitorea y analiza tu cartera apps/client/src/app/components/home-overview/home-overview.html 37 @@ -4588,7 +4616,7 @@ Ready to take control of your personal finances? - ¿Listo para tomar el control de tus finanzas personales? + ¿Estás preparado para tomar el control de tus finanzas personales? apps/client/src/app/components/home-overview/home-overview.html 12 @@ -4604,7 +4632,7 @@ this is projected to increase to - esto se proyecta a aumentar a + se proyecta que esto aumente a apps/client/src/app/pages/portfolio/fire/fire-page.html 147 @@ -4620,7 +4648,7 @@ At Ghostfolio, transparency is at the core of our values. We publish the source code as open source software (OSS) under the AGPL-3.0 license and we openly share aggregated key metrics of the platform’s operational status. - En Ghostfolio, la transparencia está en el centro de nuestros valores. Publicamos el código fuente como software de código abierto (OSS) bajo la licencia Licencia AGPL-3.0 y compartimos abiertamente métricas clave agregadas sobre el estado operativo de la plataforma. + En Ghostfolio, la transparencia está en el centro de nuestros valores. Publicamos el código fuente como software de código abierto (OSS) bajo la Licencia AGPL-3.0 y compartimos abiertamente métricas clave agregadas sobre el estado operativo de la plataforma. apps/client/src/app/pages/open/open-page.html 7 @@ -4656,10 +4684,10 @@ Job ID - ID de trabajo + ID del trabajo apps/client/src/app/components/admin-jobs/admin-jobs.html - 34 + 43 @@ -4712,14 +4740,14 @@ Currencies - Monedas + Divisas apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 131 + 130 apps/client/src/app/pages/public/public-page.html - 96 + 95 @@ -4756,7 +4784,7 @@ Check out the numerous features of Ghostfolio to manage your wealth - Descubra las numerosas funciones de Ghostfolio para gestionar su patrimonio + Descubre las numerosas funciones de Ghostfolio para gestionar tu patrimonio apps/client/src/app/pages/features/features-page.html 7 @@ -4767,11 +4795,11 @@ No se pudo analizar la configuración del scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -4832,7 +4860,7 @@ Protect your assets. Refine your personal investment strategy. - Protege tus assets. Mejora tu estrategia de inversión personal. + Protege tus activos. Mejora tu estrategia de inversión personal. apps/client/src/app/pages/landing/landing-page.html 124 @@ -4880,7 +4908,7 @@ Get access to 80’000+ tickers from over 50 exchanges - Obtén acceso a más de 80,000 tickers de más de 50 exchanges + Accede a más de 80.000 tickers de más de 50 bolsas apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 84 @@ -4912,7 +4940,7 @@ pursuing a buy & hold strategy - persiguiendo una compra & mantener estrategia + siguiendo una estrategia de comprar y mantener apps/client/src/app/pages/landing/landing-page.html 184 @@ -4920,7 +4948,7 @@ interested in getting insights of your portfolio composition - interesado en obtener información sobre la composición de tu portafolio + interesado en obtener información sobre la composición de tu cartera apps/client/src/app/pages/landing/landing-page.html 189 @@ -4936,7 +4964,7 @@ into minimalism - en el minimalismo + interesado en el minimalismo apps/client/src/app/pages/landing/landing-page.html 197 @@ -4944,7 +4972,7 @@ caring about diversifying your financial resources - preocuparse por diversificar tus recursos financieros + preocupado por diversificar tus recursos financieros apps/client/src/app/pages/landing/landing-page.html 201 @@ -5000,7 +5028,7 @@ How does Ghostfolio work? - ¿Cómo Ghostfolio work? + ¿Cómo funciona Ghostfolio? apps/client/src/app/pages/landing/landing-page.html 282 @@ -5016,7 +5044,7 @@ * no e-mail address nor credit card required - * no se requiere dirección de correo electrónico ni tarjeta de crédito + * no se necesita correo electrónico ni tarjeta de crédito apps/client/src/app/pages/landing/landing-page.html 292 @@ -5032,7 +5060,7 @@ Get valuable insights of your portfolio composition - Obtén información valiosa sobre la composición de tu portafolio + Obtén información valiosa sobre la composición de tu cartera apps/client/src/app/pages/landing/landing-page.html 316 @@ -5040,7 +5068,7 @@ Are you ready? - ¿Estás listo? + ¿Estás preparado? apps/client/src/app/pages/landing/landing-page.html 330 @@ -5064,7 +5092,7 @@ less than - menos que + menos de apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 129 @@ -5253,7 +5281,7 @@ Open Source Alternative to - Alternativa de software libre a + Alternativa de software de código abierto a apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.html 42 @@ -5261,7 +5289,7 @@ The Open Source Alternative to - La alternativa de software libre a + La alternativa de software de código abierto a apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 8 @@ -5269,7 +5297,7 @@ Are you looking for an open source alternative to ? Ghostfolio is a powerful portfolio management tool that provides individuals with a comprehensive platform to track, analyze, and optimize their investments. Whether you are an experienced investor or just starting out, Ghostfolio offers an intuitive user interface and a wide range of functionalities to help you make informed decisions and take control of your financial future. - ¿Estás buscando una alternativa de código abierto a ? Ghostfolio es una potente herramienta de gestión de carteras que ofrece a los usuarios una plataforma integral para rastrear, analizar y optimizar sus inversiones. Ya seas un inversor con experiencia o estés comenzando, Ghostfolio ofrece una interfaz intuitiva y una amplia gama de funcionalidades para ayudarte a tomar decisiones informadas y tener el control de tu futuro financiero. + ¿Estás buscando una alternativa de código abierto a ? Ghostfolio es una potente herramienta de gestión de carteras que proporciona a los usuarios una plataforma completa para seguir, analizar y optimizar sus inversiones. Ya seas un inversor con experiencia o estés comenzando, Ghostfolio ofrece una interfaz intuitiva y una amplia gama de funcionalidades para ayudarte a tomar decisiones informadas y tener el control de tu futuro financiero. apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 19 @@ -5293,7 +5321,7 @@ open-source-alternative-to - alternativa-de-software-libre-a + alternativa-de-software-de-codigo-abierto-a kebab-case libs/common/src/lib/routes/routes.ts @@ -5314,7 +5342,7 @@ Ready to take your investments to the next level? - ¿Listo para llevar sus inversiones al siguiente nivel? + ¿Estás preparado para llevar tus inversiones al siguiente nivel? apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 325 @@ -5362,7 +5390,7 @@ with your university e-mail address - con tu dirección de correo electrónico de la universidad + con tu dirección de correo electrónico universitaria apps/client/src/app/pages/pricing/pricing-page.html 348 @@ -5414,7 +5442,7 @@ One-time fee, annual account fees - Tarifa única, tarifas anuales de la cuenta + Comisión única, comisiones anuales de la cuenta apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html 33 @@ -5430,10 +5458,10 @@ Fee - Tarifa + Comisión apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 262 + 258 libs/ui/src/lib/activities-table/activities-table.component.html @@ -5454,7 +5482,7 @@ Add Tag - Agregar etiqueta + Añadir etiqueta apps/client/src/app/components/admin-tag/admin-tag.component.html 9 @@ -5462,10 +5490,10 @@ Do you really want to delete this tag? - ¿Realmente deseas eliminar esta etiqueta? + ¿Seguro que quieres eliminar esta etiqueta? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 108 @@ -5533,7 +5561,7 @@ Membresía apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 48 + 67 libs/common/src/lib/routes/routes.ts @@ -5546,7 +5574,7 @@ Request it - Solicitar + Solicítalo apps/client/src/app/pages/pricing/pricing-page.html 344 @@ -5565,12 +5593,12 @@ Perfil de activo apps/client/src/app/components/admin-jobs/admin-jobs.html - 52 + 61 Do you really want to delete this asset profile? - ¿Realmente deseas eliminar este perfil de activo? + ¿Seguro que quieres eliminar este perfil de activo? apps/client/src/app/components/admin-market-data/admin-market-data.service.ts 37 @@ -5594,7 +5622,7 @@ Ghostfolio is a personal finance dashboard to keep track of your net worth including cash, stocks, ETFs and cryptocurrencies across multiple platforms. - Ghostfolio es un dashboard de finanzas personales para hacer un seguimiento de tus activos como acciones, ETFs o criptodivisas a través de múltiples plataformas. + Ghostfolio es un panel de control de finanzas personales para hacer un seguimiento de tu patrimonio neto, incluyendo efectivo, acciones, ETFs y criptomonedas a través de múltiples plataformas. apps/client/src/app/pages/i18n/i18n-page.html 5 @@ -5630,7 +5658,7 @@ Ghostfolio vs comparison table - Ghostfolio vs tabla comparativa + Tabla comparativa de Ghostfolio vs apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 55 @@ -5646,7 +5674,7 @@ app, asset, cryptocurrency, dashboard, etf, finance, management, performance, portfolio, software, stock, trading, wealth, web3 - aplicación, activo, criptomoneda, panel, ETF, finanzas, gestión, rendimiento, cartera, software, acciones, comercio, riqueza, web3 + aplicación, activo, criptomoneda, panel, ETF, finanzas, gestión, rendimiento, cartera, software, acciones, comercio, patrimonio, web3 apps/client/src/app/pages/i18n/i18n-page.html 10 @@ -5654,7 +5682,7 @@ Oops, cash balance transfer has failed. - Oops, el saldo de efectivo no se ha transferido. + ¡Vaya! La transferencia del saldo de efectivo ha fallado. apps/client/src/app/pages/accounts/accounts-page.component.ts 341 @@ -5670,7 +5698,7 @@ Extreme Greed - Avaricia extrema + Codicia extrema libs/ui/src/lib/i18n.ts 107 @@ -5686,18 +5714,18 @@ Oops! Could not parse historical data. - ¡Ups! No se pudieron analizar los datos históricos. + ¡Vaya! No se pudieron analizar los datos históricos. libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts - 262 + 284 Do you really want to delete this system message? - ¿Realmente deseas eliminar este mensaje del sistema? + ¿Seguro que quieres eliminar este mensaje del sistema? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 206 + 209 @@ -5721,7 +5749,7 @@ Saldos de efectivo apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 148 + 146 @@ -5738,15 +5766,15 @@ Do you really want to delete this account balance? - ¿Realmente desea eliminar el saldo de esta cuenta? + ¿Seguro que quieres eliminar el saldo de esta cuenta? libs/ui/src/lib/account-balances/account-balances.component.ts - 120 + 113 If a translation is missing, kindly support us in extending it here. - Si falta una traducción, por favor ayúdenos a ampliarla. here. + Si falta una traducción, por favor ayúdanos a completarla aquí. apps/client/src/app/components/user-account-settings/user-account-settings.html 59 @@ -5757,7 +5785,7 @@ El precio actual de mercado es apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5765,7 +5793,7 @@ Prueba apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5802,7 +5830,7 @@ Oops! Could not grant access. - ¡Ups! No se pudo otorgar acceso. + ¡Vaya! No se pudo otorgar acceso. apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts 141 @@ -5834,7 +5862,7 @@ Market data is delayed for - Los datos del mercado se retrasan por + Los datos del mercado tienen un retraso de apps/client/src/app/components/portfolio-performance/portfolio-performance.component.ts 95 @@ -5853,11 +5881,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 81 + 80 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 97 + 96 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5877,7 +5905,7 @@ Cerrar posición apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 447 @@ -5890,7 +5918,7 @@ Asset Performance - Rendimiento de activos + Rendimiento de los activos apps/client/src/app/pages/portfolio/analysis/analysis-page.html 190 @@ -5898,7 +5926,7 @@ Absolute Currency Performance - Rendimiento absoluto de divisas + Rendimiento absoluto de las divisas apps/client/src/app/pages/portfolio/analysis/analysis-page.html 211 @@ -5906,7 +5934,7 @@ Currency Performance - Rendimiento de la moneda + Rendimiento de la divisa apps/client/src/app/pages/portfolio/analysis/analysis-page.html 236 @@ -5917,7 +5945,7 @@ Semana hasta la fecha libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5925,11 +5953,11 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5937,7 +5965,7 @@ Mes hasta la fecha libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5945,19 +5973,19 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 Year to date - El año hasta la fecha + Año hasta la fecha libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -5974,7 +6002,7 @@ Oops! A data provider is experiencing the hiccups. - ¡Ups! Un proveedor de datos está experimentando problemas. + ¡Vaya! Un proveedor de datos está experimentando problemas. apps/client/src/app/components/portfolio-performance/portfolio-performance.component.html 8 @@ -5993,7 +6021,7 @@ año apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6005,7 +6033,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -6013,11 +6041,11 @@ años apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -6033,7 +6061,7 @@ Recopilación de datos apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6045,7 +6073,7 @@ General apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6053,7 +6081,7 @@ Nube apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6065,7 +6093,7 @@ Autoalojamiento apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6074,7 +6102,7 @@ self-hosting - auto-alojado + autoalojado kebab-case libs/common/src/lib/routes/routes.ts @@ -6106,7 +6134,7 @@ Activo apps/client/src/app/components/home-holdings/home-holdings.component.ts - 64 + 63 @@ -6114,7 +6142,7 @@ Cerrado apps/client/src/app/components/home-holdings/home-holdings.component.ts - 65 + 64 @@ -6127,7 +6155,7 @@ Activity - Actividad + Operación apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 229 @@ -6143,10 +6171,10 @@ Execute Job - Ejecutar Tarea + Ejecutar trabajo apps/client/src/app/components/admin-jobs/admin-jobs.html - 220 + 229 @@ -6154,7 +6182,7 @@ Prioridad apps/client/src/app/components/admin-jobs/admin-jobs.html - 96 + 105 @@ -6175,7 +6203,7 @@ {VAR_PLURAL, plural, =1 {activity} other {activities}} - {VAR_PLURAL, plural, =1 {actividad} other {actividades}} + {VAR_PLURAL, plural, =1 {operación} other {operaciones}} apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html 14 @@ -6191,7 +6219,7 @@ Delete Activities - Eliminar actividades + Eliminar operaciones libs/ui/src/lib/activities-table/activities-table.component.html 69 @@ -6207,10 +6235,10 @@ Do you really want to close your Ghostfolio account? - ¿Estás seguro de querer borrar tu cuenta de Ghostfolio? + ¿Seguro que quieres eliminar tu cuenta de Ghostfolio? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 205 @@ -6239,7 +6267,7 @@ Approximation based on the top holdings of each ETF - Aproximación basada en las principales participaciones de cada ETF + Aproximación basada en las principales posiciones de cada ETF apps/client/src/app/pages/portfolio/allocations/allocations-page.html 340 @@ -6258,15 +6286,15 @@ Incluir en apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 Oops! There was an error setting up biometric authentication. - ¡Ups! Hubo un error al configurar la autenticación biométrica. + ¡Vaya! Hubo un error al configurar la autenticación biométrica. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 336 + 333 @@ -6279,15 +6307,15 @@ Benchmarks - Puntos de referencia + Índices de referencia apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 126 + 125 Delete Profiles - Borrar Perfiles + Eliminar Perfiles apps/client/src/app/components/admin-market-data/admin-market-data.html 242 @@ -6295,7 +6323,7 @@ Do you really want to delete these profiles? - Estas seguro de borrar estos perfiles? + ¿Seguro que quieres eliminar estos perfiles? apps/client/src/app/components/admin-market-data/admin-market-data.service.ts 68 @@ -6303,7 +6331,7 @@ Oops! Could not delete profiles. - ¡Ups! No se pudieron eliminar los perfiles. + ¡Vaya! No se pudieron eliminar los perfiles. apps/client/src/app/components/admin-market-data/admin-market-data.service.ts 56 @@ -6319,7 +6347,7 @@ Chart - Grafico + Gráfico apps/client/src/app/components/home-holdings/home-holdings.html 19 @@ -6330,7 +6358,7 @@ ¿Te gustaría refinar tu estrategia de inversión personal? apps/client/src/app/pages/public/public-page.html - 234 + 233 @@ -6351,7 +6379,7 @@ Budgeting - Presupuestación + Presupuestos apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts 85 @@ -6491,7 +6519,7 @@ Wealth - Riqueza + Patrimonio apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts 99 @@ -6539,7 +6567,7 @@ View Holding - Ver fondos + Ver posición libs/ui/src/lib/activities-table/activities-table.component.html 450 @@ -6662,7 +6690,7 @@ Error apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6683,7 +6711,7 @@ Oops! Could not update access. - Oops! No se pudo actualizar el acceso. + ¡Vaya! No se pudo actualizar el acceso. apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts 178 @@ -6714,7 +6742,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6746,7 +6774,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 345 + 341 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -6754,7 +6782,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 46 + 47 libs/ui/src/lib/i18n.ts @@ -6766,7 +6794,7 @@ Cerrar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6792,13 +6820,17 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 68 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 127 + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 107 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 347 + 343 libs/ui/src/lib/i18n.ts @@ -6810,7 +6842,7 @@ Rol apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 14 + 33 @@ -6834,12 +6866,12 @@ Instantánea de la cartera apps/client/src/app/components/admin-jobs/admin-jobs.html - 56 + 65 Change with currency effect Change - Cambiar con efecto de cambio dedivisa Cambiar + Cambio con el efecto de la divisa Cambio apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 63 @@ -6855,7 +6887,7 @@ Performance with currency effect Performance - Rendimiento con cambio de divisa Rendimiento + Rendimiento con el efecto de la divisa Rendimiento apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 83 @@ -6879,7 +6911,7 @@ send an e-mail to - enviar un correo electrónico a + envía un correo electrónico a apps/client/src/app/pages/about/overview/about-overview-page.html 87 @@ -6901,6 +6933,14 @@ 42 + + has been copied to the clipboard + has been copied to the clipboard + + libs/ui/src/lib/value/value.component.ts + 180 + + From the beginning Desde el principio @@ -6911,7 +6951,7 @@ Oops! Invalid currency. - ¡Ups! Moneda inválida. + ¡Vaya! Divisa no válida. apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html 48 @@ -6959,7 +6999,7 @@ to use our referral link and get a Ghostfolio Premium membership for one year - para usar nuestro enlace de referido y obtener una membresía Ghostfolio Premium por un año + para utilizar nuestro enlace de recomendación y obtener una membresía de Ghostfolio Premium por un año apps/client/src/app/pages/pricing/pricing-page.html 340 @@ -7039,7 +7079,7 @@ Ghostfolio is a lightweight wealth management application for individuals to keep track of stocks, ETFs or cryptocurrencies and make solid, data-driven investment decisions. - Ghostfolio es una aplicación de gestión de patrimonio para aquellos individuos que desean realizar un seguimiento de acciones, ETFs o criptomonedas y tomar decisiones de inversión sólidas y basadas en datos. + Ghostfolio es una aplicación ligera de gestión de patrimonio para que las personas realicen un seguimiento de acciones, ETFs o criptomonedas y tomen decisiones de inversión sólidas basadas en datos. apps/client/src/app/pages/about/overview/about-overview-page.html 10 @@ -7047,7 +7087,7 @@ Oops! Could not find any assets. - ¡Ups! No se pudieron encontrar activos. + ¡Vaya! No se pudieron encontrar activos. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html 40 @@ -7066,12 +7106,12 @@ Configurar clave API apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 Get access to 80’000+ tickers from over 50 exchanges - Accede a más de 80’000 tickers de más de 50 bolsas + Accede a más de 80.000 tickers de más de 50 bolsas libs/ui/src/lib/i18n.ts 26 @@ -7102,7 +7142,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 45 + 44 libs/common/src/lib/routes/routes.ts @@ -7118,7 +7158,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 34 + 33 libs/common/src/lib/routes/routes.ts @@ -7153,7 +7193,7 @@ Threshold range - Rango umbral + Rango del umbral apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html 9 @@ -7172,7 +7212,7 @@ de apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7180,7 +7220,7 @@ solicitudes diarias apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7188,20 +7228,20 @@ Eliminar clave API apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 Do you really want to delete the API key? - ¿Realmente deseas eliminar la clave API? + ¿Seguro que quieres eliminar la clave API? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 Please enter your Ghostfolio API key: - Ingrese su clave API de Ghostfolio: + Ingresa tu clave API de Ghostfolio: apps/client/src/app/pages/api/api-page.component.ts 43 @@ -7209,14 +7249,14 @@ API Requests Today - Solicitudes de API hoy + Solicitudes de API de hoy apps/client/src/app/components/admin-users/admin-users.html 161 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 98 + 117 @@ -7229,7 +7269,7 @@ Set this API key in your self-hosted environment: - Configure esta clave API en su entorno autohospedado: + Configura esta clave API en tu entorno autoalojado: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts 151 @@ -7245,7 +7285,7 @@ Do you really want to generate a new API key? - ¿Realmente desea generar una nueva clave API? + ¿Seguro que quieres generar una nueva clave API? apps/client/src/app/components/user-account-membership/user-account-membership.component.ts 159 @@ -7261,7 +7301,7 @@ Generate Ghostfolio Premium Data Provider API key for self-hosted environments... - Genere la clave API del proveedor de datos premium de Ghostfolio para entornos autohospedados... + Genera la clave API del proveedor de datos premium de Ghostfolio para entornos autoalojados... libs/ui/src/lib/membership-card/membership-card.component.html 29 @@ -7269,7 +7309,7 @@ out of - fuera de + de apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html 56 @@ -7277,7 +7317,7 @@ rules align with your portfolio. - Las reglas se alinean con su cartera. + reglas se alinean con tu cartera. apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html 58 @@ -7285,10 +7325,10 @@ Save - Ahorrar + Guardar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7324,11 +7364,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 356 + 352 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 48 + 49 @@ -7340,7 +7380,7 @@ apps/client/src/app/components/user-account-access/user-account-access.component.ts - 260 + 257 @@ -7353,7 +7393,7 @@ Check the system status at - Verificar el estado del sistema en + Verifica el estado del sistema en apps/client/src/app/pages/about/overview/about-overview-page.html 57 @@ -7364,12 +7404,12 @@ Por favor, ingresa tu clave API de Ghostfolio. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 Change with currency effect - Cambiar con el efecto del tipo de cambio de divisa + Cambio con el efecto de la divisa apps/client/src/app/pages/portfolio/analysis/analysis-page.html 116 @@ -7377,10 +7417,10 @@ AI prompt has been copied to the clipboard - El aviso de IA ha sido copiado al portapapeles + El prompt para la IA ha sido copiado al portapapeles apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 201 + 199 @@ -7393,10 +7433,10 @@ Lazy - Perezoso + Bajo demanda apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7404,7 +7444,7 @@ Instantáneo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7412,7 +7452,7 @@ Precio de mercado por defecto apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7420,7 +7460,7 @@ Modo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7428,7 +7468,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7436,7 +7476,7 @@ Encabezados de solicitud HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7444,7 +7484,7 @@ final del día apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7452,7 +7492,7 @@ en tiempo real apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7460,7 +7500,7 @@ Abrir Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 202 + 200 @@ -7473,14 +7513,14 @@ Change - Cambiar + Cambio libs/ui/src/lib/holdings-table/holdings-table.component.html 138 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 @@ -7492,7 +7532,7 @@ apps/client/src/app/components/home-overview/home-overview.component.ts - 55 + 54 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -7500,11 +7540,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 381 + 390 @@ -7517,7 +7557,7 @@ Copy portfolio data to clipboard for AI prompt - Copiar los datos del portafolio al portapapeles para el aviso de IA + Copiar los datos de la cartera al portapapeles para el prompt de IA apps/client/src/app/pages/portfolio/analysis/analysis-page.html 42 @@ -7525,7 +7565,7 @@ Copy AI prompt to clipboard for analysis - Copiar el aviso de IA al portapapeles para análisis + Copiar el prompt de IA al portapapeles para análisis apps/client/src/app/pages/portfolio/analysis/analysis-page.html 67 @@ -7533,7 +7573,7 @@ Total amount - Cantidad total + Importe total apps/client/src/app/pages/portfolio/analysis/analysis-page.html 95 @@ -7565,7 +7605,7 @@ Terms and Conditions - Terminos y condiciones + Términos y Condiciones apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 15 @@ -7608,27 +7648,27 @@ Token de seguridad apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 235 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 170 + 167 Do you really want to generate a new security token for this user? - ¿Realmente deseas generar un nuevo token de seguridad para este usuario? + ¿Seguro que quieres generar un nuevo token de seguridad para este usuario? apps/client/src/app/components/admin-users/admin-users.component.ts - 241 + 240 Find account, holding or page... - Buscar cuenta, posición o página... + Busca una cuenta, posición o página... libs/ui/src/lib/assistant/assistant.component.ts - 151 + 115 @@ -7682,7 +7722,7 @@ and I agree to the Terms of Service. - y acepto los Términos del servicio. + y acepto los Términos de servicio. apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 34 @@ -7693,7 +7733,7 @@ () ya está en uso. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7701,7 +7741,7 @@ Ocurrió un error al actualizar a (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7765,7 +7805,7 @@ alguien apps/client/src/app/pages/public/public-page.component.ts - 59 + 61 @@ -7794,15 +7834,15 @@ Do you really want to delete this item? - ¿Realmente deseas eliminar este elemento? + ¿Seguro que quieres eliminar este elemento? libs/ui/src/lib/benchmark/benchmark.component.ts - 144 + 139 Log out - Finalizar la sesión + Cerrar sesión apps/client/src/app/components/header/header.component.html 329 @@ -7810,7 +7850,7 @@ Calculations are based on delayed market data and may not be displayed in real-time. - Los cálculos se basan en datos de mercado retrasados ​​y es posible que no se muestren en tiempo real. + Los cálculos se basan en datos de mercado con retraso y es posible que no se muestren en tiempo real. apps/client/src/app/components/home-market/home-market.html 45 @@ -7822,7 +7862,7 @@ changelog - registro-decambios + registro-de-cambios kebab-case libs/common/src/lib/routes/routes.ts @@ -7838,7 +7878,7 @@ La cuenta de usuario de demostración se ha sincronizado. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 274 + 277 @@ -7851,7 +7891,7 @@ Set up - Fondo de Emergencia: Establecer + Establecer apps/client/src/app/pages/i18n/i18n-page.html 145 @@ -7873,25 +7913,25 @@ 150 - + Fee Ratio - Relación de tarifas + Relación de comisiones apps/client/src/app/pages/i18n/i18n-page.html 152 - - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - Las tarifas superan el ${thresholdMax}% de su inversión inicial (${feeRatio}%) + + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + Las comisiones superan el ${thresholdMax}% de tu volumen total de inversión (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 - - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - Las tarifas no superan el ${thresholdMax}% de su inversión inicial (${feeRatio}%) + + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + Las comisiones no superan el ${thresholdMax}% de tu volumen total de inversión (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -7923,7 +7963,7 @@ Open Source Alternative to - Alternativa de software libre a + Alternativa de software de código abierto a libs/common/src/lib/routes/routes.ts 326 @@ -7985,7 +8025,7 @@ Fuel your self-hosted Ghostfolio with a powerful data provider to access 80,000+ tickers from over 50 exchanges worldwide. - Alimenta tu Ghostfolio autoalojado con un proveedor de datos potente para acceder a más de 80.000 tickers de más de 50 intercambios a nivel mundial. + Alimenta tu Ghostfolio autoalojado con un proveedor de datos potente para acceder a más de 80.000 tickers de más de 50 bolsas a nivel mundial. apps/client/src/app/components/admin-settings/admin-settings.component.html 16 @@ -8001,7 +8041,7 @@ Learn more - Aprender más + Más información apps/client/src/app/components/admin-settings/admin-settings.component.html 38 @@ -8052,7 +8092,7 @@ Mes actual apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 @@ -8060,11 +8100,11 @@ nuevo apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts - 56 + 53 @@ -8093,7 +8133,7 @@ Equity - Acciones + Renta variable apps/client/src/app/pages/i18n/i18n-page.html 41 @@ -8157,7 +8197,7 @@ Investment: Base Currency - Inversión: Moneda base + Inversión: Divisa base apps/client/src/app/pages/i18n/i18n-page.html 85 @@ -8165,7 +8205,7 @@ The major part of your current investment is not in your base currency (${baseCurrencyValueRatio}% in ${baseCurrency}) - La mayor parte de tu inversión actual no está en tu moneda base (${baseCurrencyValueRatio}% en ${baseCurrency}) + La mayor parte de tu inversión actual no está en tu divisa base (${baseCurrencyValueRatio}% en ${baseCurrency}) apps/client/src/app/pages/i18n/i18n-page.html 88 @@ -8173,7 +8213,7 @@ The major part of your current investment is in your base currency (${baseCurrencyValueRatio}% in ${baseCurrency}) - La mayor parte de tu inversión actual está en tu moneda base (${baseCurrencyValueRatio}% en ${baseCurrency}) + La mayor parte de tu inversión actual está en tu divisa base (${baseCurrencyValueRatio}% en ${baseCurrency}) apps/client/src/app/pages/i18n/i18n-page.html 92 @@ -8218,10 +8258,10 @@ Do you really want to generate a new security token? - ¿Realmente deseas generar un nuevo token de seguridad? + ¿Seguro que quieres generar un nuevo token de seguridad? apps/client/src/app/components/user-account-access/user-account-access.component.ts - 175 + 172 @@ -8234,7 +8274,7 @@ If you encounter a bug, would like to suggest an improvement or a new feature, please join the Ghostfolio Slack community, post to @ghostfolio_ - Si encuentras un error, deseas sugerir una mejora o una nueva característica, por favor únete a la comunidad Ghostfolio Slack, publica en @ghostfolio_ + Si encuentras un error, deseas sugerir una mejora o una nueva funcionalidad, por favor únete a la comunidad de Ghostfolio en Slack y publica en @ghostfolio_ apps/client/src/app/pages/about/overview/about-overview-page.html 69 @@ -8245,7 +8285,7 @@ Acciones apps/client/src/app/components/markets/markets.component.ts - 52 + 51 apps/client/src/app/pages/features/features-page.html @@ -8257,7 +8297,7 @@ Criptomonedas apps/client/src/app/components/markets/markets.component.ts - 53 + 52 apps/client/src/app/pages/features/features-page.html @@ -8266,7 +8306,7 @@ - + apps/client/src/app/components/admin-users/admin-users.html 39 @@ -8277,7 +8317,7 @@ Gestionar perfil de activo apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 466 + 471 @@ -8310,7 +8350,7 @@ Account Cluster Risks - Riesgos de grupo de cuentas + Riesgos de agrupación de cuentas apps/client/src/app/pages/i18n/i18n-page.html 14 @@ -8318,7 +8358,7 @@ Asset Class Cluster Risks - Riesgos de grupo de clases de activos + Riesgos de agrupación de tipos de activos apps/client/src/app/pages/i18n/i18n-page.html 39 @@ -8326,7 +8366,7 @@ Currency Cluster Risks - Riesgos del clúster de divisas + Riesgos de agrupación de divisas apps/client/src/app/pages/i18n/i18n-page.html 83 @@ -8334,7 +8374,7 @@ Economic Market Cluster Risks - Riesgos del clúster de mercados económicos + Riesgos de agrupación de mercados económicos apps/client/src/app/pages/i18n/i18n-page.html 106 @@ -8366,7 +8406,7 @@ Buying Power - Poder de compra + Poder adquisitivo apps/client/src/app/pages/i18n/i18n-page.html 71 @@ -8374,7 +8414,7 @@ Your buying power is below ${thresholdMin} ${baseCurrency} - Tu poder de compra es inferior a ${thresholdMin} ${baseCurrency} + Tu poder adquisitivo está por debajo de ${thresholdMin} ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 73 @@ -8382,7 +8422,7 @@ Your buying power is 0 ${baseCurrency} - Tu poder de compra es 0 ${baseCurrency} + Tu poder adquisitivo es 0 ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 77 @@ -8390,7 +8430,7 @@ Your buying power exceeds ${thresholdMin} ${baseCurrency} - Tu poder de compra excede ${thresholdMin} ${baseCurrency} + Tu poder adquisitivo supera ${thresholdMin} ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 80 @@ -8398,7 +8438,7 @@ Regional Market Cluster Risks - Riesgos de los grupos de mercados regionales + Riesgos de agrupación de mercados regionales apps/client/src/app/pages/i18n/i18n-page.html 163 @@ -8422,7 +8462,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) exceeds ${thresholdMax}% - La contribución a los mercados desarrollados de tu inversión actual (${developedMarketsValueRatio}%) supera el ${thresholdMax}% + La contribución a mercados desarrollados de tu inversión actual (${developedMarketsValueRatio}%) supera ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 112 @@ -8430,7 +8470,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is below ${thresholdMin}% - La contribución a los mercados desarrollados de tu inversión actual (${developedMarketsValueRatio}%) es inferior al ${thresholdMin}% + La contribución a mercados desarrollados de tu inversión actual (${developedMarketsValueRatio}%) es inferior a ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 117 @@ -8438,7 +8478,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - La contribución a los mercados desarrollados de tu inversión actual (${developedMarketsValueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% + La contribución a mercados desarrollados de tu inversión actual (${developedMarketsValueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 122 @@ -8454,7 +8494,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) exceeds ${thresholdMax}% - La contribución a los mercados emergentes de tu inversión actual (${emergingMarketsValueRatio}%) supera el ${thresholdMax}% + La contribución a mercados emergentes de tu inversión actual (${emergingMarketsValueRatio}%) supera ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 130 @@ -8462,7 +8502,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is below ${thresholdMin}% - La contribución a los mercados emergentes de tu inversión actual (${emergingMarketsValueRatio}%) es inferior al ${thresholdMin}% + La contribución a mercados emergentes de tu inversión actual (${emergingMarketsValueRatio}%) es inferior a ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 135 @@ -8470,7 +8510,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - La contribución a los mercados emergentes de tu inversión actual (${emergingMarketsValueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% + La contribución a mercados emergentes de tu inversión actual (${emergingMarketsValueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 140 @@ -8486,7 +8526,7 @@ Your net worth is managed by 0 accounts - Su patrimonio neto es administrado por 0 cuentas + Tu patrimonio neto es administrado por 0 cuentas apps/client/src/app/pages/i18n/i18n-page.html 33 @@ -8502,7 +8542,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - La contribución al mercado de Asia-Pacífico de tu inversión actual (${valueRatio}%) supera el ${thresholdMax}% + La contribución al mercado Asiático-Pacífico de tu inversión actual (${valueRatio}%) supera ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 167 @@ -8510,7 +8550,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - La contribución al mercado de Asia-Pacífico de tu inversión actual (${valueRatio}%) es inferior al ${thresholdMin}% + La contribución al mercado Asiático-Pacífico de tu inversión actual (${valueRatio}%) es inferior a ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 171 @@ -8518,7 +8558,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - La contribución al mercado de Asia-Pacífico de tu inversión actual (${valueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% + La contribución al mercado Asiático-Pacífico de tu inversión actual (${valueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 175 @@ -8534,7 +8574,7 @@ The Emerging Markets contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - La contribución a los mercados emergentes de tu inversión actual (${valueRatio}%) supera el ${thresholdMax}% + La contribución a mercados emergentes de tu inversión actual (${valueRatio}%) supera ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 183 @@ -8542,7 +8582,7 @@ The Emerging Markets contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - La contribución a los mercados emergentes de tu inversión actual (${valueRatio}%) es inferior al ${thresholdMin}% + La contribución a mercados emergentes de tu inversión actual (${valueRatio}%) es inferior a ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 187 @@ -8550,7 +8590,7 @@ The Emerging Markets contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - La contribución a los mercados emergentes de tu inversión actual (${valueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% + La contribución a mercados emergentes de tu inversión actual (${valueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 191 @@ -8566,7 +8606,7 @@ The Europe market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - La contribución al mercado europeo de tu inversión actual (${valueRatio}%) supera el ${thresholdMax}% + La contribución al mercado europeo de tu inversión actual (${valueRatio}%) supera ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 197 @@ -8574,7 +8614,7 @@ The Europe market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - La contribución al mercado europeo de tu inversión actual (${valueRatio}%) es inferior al ${thresholdMin}% + La contribución al mercado europeo de tu inversión actual (${valueRatio}%) es inferior a ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 201 @@ -8598,7 +8638,7 @@ The Japan market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - La contribución al mercado japonés de su inversión actual (${valueRatio}%) supera el ${thresholdMax}% + La contribución al mercado japonés de tu inversión actual (${valueRatio}%) supera el ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 211 @@ -8606,7 +8646,7 @@ The Japan market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - La contribución al mercado japonés de su inversión actual (${valueRatio}%) es inferior a ${thresholdMin}% + La contribución al mercado japonés de tu inversión actual (${valueRatio}%) es inferior a ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 215 @@ -8614,7 +8654,7 @@ The Japan market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - La contribución al mercado japonés de su inversión actual (${valueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% + La contribución al mercado japonés de tu inversión actual (${valueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 219 @@ -8630,7 +8670,7 @@ The North America market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - La contribución del mercado de América del Norte de su inversión actual (${valueRatio}%) supera el ${thresholdMax}% + La contribución al mercado de América del Norte de tu inversión actual (${valueRatio}%) supera el ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 225 @@ -8638,7 +8678,7 @@ The North America market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - La contribución al mercado de América del Norte de su inversión actual (${valueRatio}%) es inferior a ${thresholdMin}% + La contribución al mercado de América del Norte de tu inversión actual (${valueRatio}%) es inferior a ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 229 @@ -8646,7 +8686,7 @@ The North America market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - La contribución al mercado de América del Norte de su inversión actual (${valueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% + La contribución al mercado de América del Norte de tu inversión actual (${valueRatio}%) está dentro del rango de ${thresholdMin}% y ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 233 @@ -8666,7 +8706,7 @@ Join the Ghostfolio Slack community - Únete a la comunidad de Ghostfolio Slack + Únete a la comunidad de Ghostfolio en Slack apps/client/src/app/pages/about/overview/about-overview-page.html 109 @@ -8674,7 +8714,7 @@ Follow Ghostfolio on X (formerly Twitter) - Siga a Ghostfolio en X (anteriormente Twitter) + Sigue a Ghostfolio en X (anteriormente Twitter) apps/client/src/app/pages/about/overview/about-overview-page.html 118 @@ -8697,12 +8737,12 @@ Fecha de registro apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 26 + 45 Follow Ghostfolio on LinkedIn - Siga a Ghostfolio en LinkedIn + Sigue a Ghostfolio en LinkedIn apps/client/src/app/pages/about/overview/about-overview-page.html 147 @@ -8710,7 +8750,7 @@ Ghostfolio is an independent & bootstrapped business - Ghostfolio es una empresa independiente y autónoma + Ghostfolio es una empresa independiente y autofinanciada apps/client/src/app/pages/about/overview/about-overview-page.html 157 @@ -8718,7 +8758,7 @@ Support Ghostfolio - Soporte Ghostfolio + Apoya a Ghostfolio apps/client/src/app/pages/about/overview/about-overview-page.html 166 diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index 0da1c3f6a..eec144e92 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -42,7 +42,7 @@ Type apps/client/src/app/components/admin-jobs/admin-jobs.html - 48 + 57 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -114,7 +114,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -126,7 +126,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -142,7 +142,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 139 + 135 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -186,7 +186,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -198,7 +198,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 145 + 141 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -234,15 +234,15 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 205 + 201 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 208 + 204 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 211 + 207 libs/ui/src/lib/account-balances/account-balances.component.html @@ -290,11 +290,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -322,11 +322,15 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 libs/ui/src/lib/account-balances/account-balances.component.html @@ -350,7 +354,7 @@ Voulez-vous vraiment supprimer ce compte ? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 150 + 148 @@ -358,7 +362,7 @@ Source Données apps/client/src/app/components/admin-jobs/admin-jobs.html - 82 + 91 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -370,7 +374,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 155 + 151 libs/ui/src/lib/i18n.ts @@ -382,7 +386,7 @@ Tentatives apps/client/src/app/components/admin-jobs/admin-jobs.html - 120 + 129 @@ -390,7 +394,7 @@ Créé apps/client/src/app/components/admin-jobs/admin-jobs.html - 134 + 143 @@ -398,7 +402,7 @@ Terminé apps/client/src/app/components/admin-jobs/admin-jobs.html - 143 + 152 @@ -406,11 +410,11 @@ Statut apps/client/src/app/components/admin-jobs/admin-jobs.html - 152 + 161 apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -426,7 +430,7 @@ Supprimer Tâches apps/client/src/app/components/admin-jobs/admin-jobs.html - 193 + 202 @@ -434,7 +438,7 @@ Profils d’Actifs apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -446,11 +450,11 @@ Données historiques du marché apps/client/src/app/components/admin-jobs/admin-jobs.html - 54 + 63 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -458,7 +462,7 @@ Voir Données apps/client/src/app/components/admin-jobs/admin-jobs.html - 208 + 217 @@ -466,7 +470,7 @@ Voir la Stacktrace apps/client/src/app/components/admin-jobs/admin-jobs.html - 216 + 225 @@ -474,7 +478,7 @@ Supprimer Tâche apps/client/src/app/components/admin-jobs/admin-jobs.html - 224 + 233 @@ -490,7 +494,7 @@ Date apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 161 + 157 libs/ui/src/lib/account-balances/account-balances.component.html @@ -526,7 +530,7 @@ Filtrer par... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 385 + 383 @@ -566,7 +570,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 44 + 40 @@ -634,7 +638,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 53 + 72 @@ -646,7 +650,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -654,7 +658,7 @@ apps/client/src/app/pages/public/public-page.html - 114 + 113 @@ -666,7 +670,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -678,7 +682,7 @@ Équivalence de Symboles apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -686,7 +690,7 @@ Note apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -694,7 +698,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 275 + 271 @@ -702,7 +706,7 @@ Voulez-vous vraiment supprimer ce code promotionnel ? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 193 + 196 @@ -710,7 +714,7 @@ Voulez-vous vraiment vider le cache ? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 230 + 233 @@ -718,7 +722,7 @@ Veuillez définir votre message système : apps/client/src/app/components/admin-overview/admin-overview.component.ts - 250 + 253 @@ -758,7 +762,7 @@ Étiquettes apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -878,7 +882,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 89 + 108 @@ -894,11 +898,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -938,7 +942,7 @@ Référence apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -974,7 +978,7 @@ apps/client/src/app/components/header/header.component.ts - 297 + 298 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -994,15 +998,15 @@ Oups! Jeton de Sécurité Incorrect. apps/client/src/app/components/header/header.component.ts - 312 + 313 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 154 + 152 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 193 + 191 @@ -1010,7 +1014,7 @@ Gérer les Activités apps/client/src/app/components/home-holdings/home-holdings.html - 67 + 64 @@ -1018,11 +1022,11 @@ Peur apps/client/src/app/components/home-market/home-market.component.ts - 42 + 41 apps/client/src/app/components/markets/markets.component.ts - 47 + 46 libs/ui/src/lib/i18n.ts @@ -1034,11 +1038,11 @@ Avidité apps/client/src/app/components/home-market/home-market.component.ts - 43 + 42 apps/client/src/app/components/markets/markets.component.ts - 48 + 47 libs/ui/src/lib/i18n.ts @@ -1062,7 +1066,7 @@ Montant Total apps/client/src/app/components/investment-chart/investment-chart.component.ts - 143 + 146 @@ -1174,7 +1178,7 @@ Performance Absolue Brute apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 73 + 77 @@ -1182,7 +1186,7 @@ Performance Absolue Nette apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 107 + 111 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -1194,7 +1198,7 @@ Performance nette apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 123 + 127 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -1206,7 +1210,7 @@ Actifs Totaux apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 149 + 153 @@ -1214,7 +1218,7 @@ Pouvoir d’Achat apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 241 + 248 @@ -1222,7 +1226,7 @@ Exclus de l’Analyse apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 267 + 274 @@ -1230,7 +1234,7 @@ Fortune apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 317 + 324 @@ -1238,7 +1242,7 @@ Performance annualisée apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 331 + 338 @@ -1274,7 +1278,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 189 + 185 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1290,7 +1294,7 @@ Signaler une Erreur de Données apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 451 + 456 @@ -1302,7 +1306,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 363 + 362 @@ -1310,11 +1314,11 @@ CDA apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -1322,11 +1326,11 @@ 1A apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -1334,11 +1338,11 @@ 5A apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -1354,11 +1358,11 @@ Max apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 217 libs/ui/src/lib/assistant/assistant.component.ts - 415 + 414 @@ -1382,7 +1386,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 195 + 194 @@ -1394,7 +1398,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 193 + 192 @@ -1410,7 +1414,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 196 + 195 @@ -1470,7 +1474,7 @@ Auto apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 70 + 69 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1514,7 +1518,7 @@ Voulez-vous vraiment supprimer cette méthode de connexion ? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 279 @@ -1582,7 +1586,7 @@ Paramètres régionaux apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1662,7 +1666,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 11 + 30 @@ -1710,11 +1714,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 375 + 381 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 66 + 85 apps/client/src/app/pages/accounts/accounts-page.html @@ -1758,7 +1762,7 @@ Données du marché apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 398 + 403 libs/common/src/lib/routes/routes.ts @@ -1902,7 +1906,7 @@ Puisque vous êtes déjà connecté·e, vous ne pouvez pas accéder au compte de démonstration. apps/client/src/app/pages/demo/demo-page.component.ts - 35 + 32 @@ -1990,7 +1994,7 @@ Marchés apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -2014,7 +2018,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 40 + 39 libs/common/src/lib/routes/routes.ts @@ -2042,7 +2046,7 @@ apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 115 + 113 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -2062,11 +2066,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 342 + 348 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 75 + 94 apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -2094,7 +2098,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 @@ -2138,7 +2142,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 124 + 120 @@ -2146,7 +2150,7 @@ Prix Unitaire apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 214 + 210 libs/ui/src/lib/activities-table/activities-table.component.html @@ -2158,7 +2162,7 @@ Import des données... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 175 + 174 @@ -2166,7 +2170,7 @@ L’import est terminé apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 185 + 184 @@ -2182,7 +2186,7 @@ Validation des données... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 299 + 293 @@ -2218,7 +2222,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 71 + 67 @@ -2310,7 +2314,7 @@ apps/client/src/app/pages/public/public-page.html - 151 + 150 @@ -2334,7 +2338,7 @@ apps/client/src/app/pages/public/public-page.html - 168 + 167 @@ -2342,7 +2346,7 @@ Latest activities apps/client/src/app/pages/public/public-page.html - 211 + 210 @@ -2354,7 +2358,7 @@ apps/client/src/app/pages/public/public-page.html - 177 + 176 @@ -2366,7 +2370,7 @@ apps/client/src/app/pages/public/public-page.html - 186 + 185 @@ -2402,7 +2406,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 365 + 372 apps/client/src/app/pages/features/features-page.html @@ -2410,11 +2414,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 202 + 198 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 76 + 75 libs/ui/src/lib/i18n.ts @@ -2434,7 +2438,7 @@ Dépôt libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 377 + 404 @@ -2442,7 +2446,7 @@ Mensuel apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 91 + 90 @@ -2546,7 +2550,7 @@ Continents apps/client/src/app/pages/public/public-page.html - 132 + 131 @@ -2562,7 +2566,7 @@ Ghostfolio vous aide à garder un aperçu de votre patrimoine. apps/client/src/app/pages/public/public-page.html - 238 + 237 @@ -2590,7 +2594,7 @@ apps/client/src/app/pages/public/public-page.html - 242 + 241 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -2644,6 +2648,10 @@ apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 88 + + libs/ui/src/lib/value/value.component.html + 18 + Resources @@ -2696,6 +2704,10 @@ Overview Aperçu + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 7 + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 114 @@ -2714,11 +2726,11 @@ apps/client/src/app/pages/admin/admin-page.component.ts - 48 + 45 apps/client/src/app/pages/resources/resources-page.component.ts - 30 + 29 libs/common/src/lib/routes/routes.ts @@ -2798,7 +2810,7 @@ Voulez-vous vraiment supprimer cette activité ? libs/ui/src/lib/activities-table/activities-table.component.ts - 288 + 292 @@ -2850,11 +2862,11 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 352 + 359 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 387 + 414 libs/ui/src/lib/i18n.ts @@ -2866,7 +2878,7 @@ Épargne libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 397 + 424 @@ -2894,7 +2906,7 @@ Montrer tout libs/ui/src/lib/holdings-table/holdings-table.component.html - 216 + 212 @@ -2930,7 +2942,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2938,7 +2950,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 290 + 286 libs/ui/src/lib/i18n.ts @@ -2962,7 +2974,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2970,7 +2982,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 309 + 305 libs/ui/src/lib/i18n.ts @@ -2982,7 +2994,7 @@ Fonds d’Urgence apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 164 + 168 apps/client/src/app/pages/features/features-page.html @@ -3002,7 +3014,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 437 + 451 @@ -3010,7 +3022,7 @@ Symbole apps/client/src/app/components/admin-jobs/admin-jobs.html - 68 + 77 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -3026,7 +3038,7 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 315 + 318 libs/ui/src/lib/i18n.ts @@ -3050,7 +3062,7 @@ Cash apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 212 + 219 libs/ui/src/lib/i18n.ts @@ -3098,7 +3110,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 35 + 54 @@ -3230,7 +3242,7 @@ apps/client/src/app/pages/public/public-page.html - 196 + 195 libs/ui/src/lib/benchmark/benchmark.component.html @@ -3238,11 +3250,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 439 + 453 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 452 + 467 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3274,7 +3286,7 @@ Annuel apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 91 @@ -3282,7 +3294,7 @@ Importer Dividendes apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 137 + 136 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3298,7 +3310,7 @@ Valide jusqu’au apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -3337,6 +3349,14 @@ 22 + + No Activities + No Activities + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 145 + + Retirement Provision Réserve pour retraite @@ -3610,11 +3630,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -3634,7 +3654,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 88 + 92 @@ -3798,7 +3818,7 @@ Voulez-vous vraiment supprimer toutes vos activités ? libs/ui/src/lib/activities-table/activities-table.component.ts - 278 + 282 @@ -3809,6 +3829,14 @@ 306 + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 + + By By @@ -3830,7 +3858,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 @@ -3846,11 +3874,11 @@ Lien apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3866,7 +3894,7 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -3874,7 +3902,7 @@ Voulez-vous vraiment supprimer cette plateforme ? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 111 @@ -3882,7 +3910,7 @@ Platformes apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -3890,7 +3918,7 @@ Mettre à jour le Solde apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 112 + 108 @@ -4070,7 +4098,7 @@ Dettes apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 295 + 302 apps/client/src/app/pages/features/features-page.html @@ -4226,7 +4254,7 @@ Configuration du Scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -4470,7 +4498,7 @@ ETF sans Pays apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 136 + 135 @@ -4478,7 +4506,7 @@ ETF sans Secteurs apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 141 + 140 @@ -4486,7 +4514,7 @@ Actifs apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 226 + 233 @@ -4658,7 +4686,7 @@ Job ID apps/client/src/app/components/admin-jobs/admin-jobs.html - 34 + 43 @@ -4714,11 +4742,11 @@ Devises apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 131 + 130 apps/client/src/app/pages/public/public-page.html - 96 + 95 @@ -4766,11 +4794,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -5432,7 +5460,7 @@ Frais apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 262 + 258 libs/ui/src/lib/activities-table/activities-table.component.html @@ -5464,7 +5492,7 @@ Confirmez la suppression de ce tag ? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 108 @@ -5532,7 +5560,7 @@ Statut apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 48 + 67 libs/common/src/lib/routes/routes.ts @@ -5564,7 +5592,7 @@ Profil d’Actif apps/client/src/app/components/admin-jobs/admin-jobs.html - 52 + 61 @@ -5688,7 +5716,7 @@ Oops! Echec du parsing des données historiques. libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts - 262 + 284 @@ -5696,7 +5724,7 @@ Confirmer la suppresion de ce message système? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 206 + 209 @@ -5720,7 +5748,7 @@ Cash Balances apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 148 + 146 @@ -5740,7 +5768,7 @@ Voulez-vous vraiment supprimer ce solde de compte ? libs/ui/src/lib/account-balances/account-balances.component.ts - 120 + 113 @@ -5756,7 +5784,7 @@ Le prix actuel du marché est apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5764,7 +5792,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5852,11 +5880,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 81 + 80 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 97 + 96 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5876,7 +5904,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 447 @@ -5916,7 +5944,7 @@ Week to date libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5924,11 +5952,11 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5936,7 +5964,7 @@ Month to date libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5944,11 +5972,11 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5956,7 +5984,7 @@ Year to date libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -5992,7 +6020,7 @@ année apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6004,7 +6032,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -6012,11 +6040,11 @@ années apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -6032,7 +6060,7 @@ Collecter les données apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6044,7 +6072,7 @@ Général apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6052,7 +6080,7 @@ Cloud apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6064,7 +6092,7 @@ Self-Hosting apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6105,7 +6133,7 @@ Actif apps/client/src/app/components/home-holdings/home-holdings.component.ts - 64 + 63 @@ -6113,7 +6141,7 @@ Clôturé apps/client/src/app/components/home-holdings/home-holdings.component.ts - 65 + 64 @@ -6145,7 +6173,7 @@ Execute la tâche apps/client/src/app/components/admin-jobs/admin-jobs.html - 220 + 229 @@ -6153,7 +6181,7 @@ Priorité apps/client/src/app/components/admin-jobs/admin-jobs.html - 96 + 105 @@ -6209,7 +6237,7 @@ Confirmer la suppresion de votre compte Ghostfolio ? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 205 @@ -6257,7 +6285,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6265,7 +6293,7 @@ Oops! Une erreur s’est produite lors de la configuration de l’authentification biométrique. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 336 + 333 @@ -6281,7 +6309,7 @@ Benchmarks apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 126 + 125 @@ -6329,7 +6357,7 @@ Souhaitez-vous affiner votre stratégie d’investissement personnelle? apps/client/src/app/pages/public/public-page.html - 234 + 233 @@ -6661,7 +6689,7 @@ Erreur apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6713,7 +6741,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6745,7 +6773,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 345 + 341 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -6753,7 +6781,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 46 + 47 libs/ui/src/lib/i18n.ts @@ -6765,7 +6793,7 @@ Fermer apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6791,13 +6819,17 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 68 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 127 + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 107 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 347 + 343 libs/ui/src/lib/i18n.ts @@ -6809,7 +6841,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 14 + 33 @@ -6833,7 +6865,7 @@ Résumé du portefeuille apps/client/src/app/components/admin-jobs/admin-jobs.html - 56 + 65 @@ -6900,6 +6932,14 @@ 42 + + has been copied to the clipboard + has been copied to the clipboard + + libs/ui/src/lib/value/value.component.ts + 180 + + From the beginning Depuis le début @@ -7065,7 +7105,7 @@ Définir clé API apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7101,7 +7141,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 45 + 44 libs/common/src/lib/routes/routes.ts @@ -7117,7 +7157,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 34 + 33 libs/common/src/lib/routes/routes.ts @@ -7171,7 +7211,7 @@ sur apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7179,7 +7219,7 @@ requêtes journalières apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7187,7 +7227,7 @@ Retirer la clé API apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7195,7 +7235,7 @@ Voulez-vous vraiment supprimer la clé API? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7215,7 +7255,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 98 + 117 @@ -7287,7 +7327,7 @@ Sauvegarder apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7323,11 +7363,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 356 + 352 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 48 + 49 @@ -7339,7 +7379,7 @@ apps/client/src/app/components/user-account-access/user-account-access.component.ts - 260 + 257 @@ -7363,7 +7403,7 @@ Veuillez saisir votre clé API Ghostfolio. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7379,7 +7419,7 @@ Le prompt IA a été copié dans le presse-papiers apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 201 + 199 @@ -7395,7 +7435,7 @@ Paresseux apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7403,7 +7443,7 @@ Instantané apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7411,7 +7451,7 @@ Prix du marché par défaut apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7419,7 +7459,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7427,7 +7467,7 @@ Selecteur apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7435,7 +7475,7 @@ En-têtes de requête HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7443,7 +7483,7 @@ fin de journée apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7451,7 +7491,7 @@ temps réel apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7459,7 +7499,7 @@ Ouvrir Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 202 + 200 @@ -7479,7 +7519,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 @@ -7491,7 +7531,7 @@ apps/client/src/app/components/home-overview/home-overview.component.ts - 55 + 54 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -7499,11 +7539,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 381 + 390 @@ -7607,11 +7647,11 @@ Jeton de sécurité apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 235 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 170 + 167 @@ -7619,7 +7659,7 @@ Voulez-vous vraiment générer un nouveau jeton de sécurité pour cet utilisateur ? apps/client/src/app/components/admin-users/admin-users.component.ts - 241 + 240 @@ -7627,7 +7667,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 151 + 115 @@ -7692,7 +7732,7 @@ () est déjà utilisé. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7700,7 +7740,7 @@ Une erreur s’est produite lors de la mise à jour vers (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7764,7 +7804,7 @@ quelqu’un apps/client/src/app/pages/public/public-page.component.ts - 59 + 61 @@ -7796,7 +7836,7 @@ Voulez-vous vraiment supprimer cet élément? libs/ui/src/lib/benchmark/benchmark.component.ts - 144 + 139 @@ -7837,7 +7877,7 @@ Le compte utilisateur de démonstration a été synchronisé. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 274 + 277 @@ -7872,25 +7912,25 @@ 150 - + Fee Ratio - Ratio de frais + Fee Ratio apps/client/src/app/pages/i18n/i18n-page.html 152 - - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - Les frais dépassent ${thresholdMax}% de votre investissement initial (${feeRatio}%) + + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 - - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - Les frais ne dépassent pas ${thresholdMax}% de votre investissement initial (${feeRatio}%) + + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -8051,7 +8091,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 @@ -8059,11 +8099,11 @@ new apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts - 56 + 53 @@ -8220,7 +8260,7 @@ Voulez-vous vraiment générer un nouveau jeton de sécurité? apps/client/src/app/components/user-account-access/user-account-access.component.ts - 175 + 172 @@ -8244,7 +8284,7 @@ Actions apps/client/src/app/components/markets/markets.component.ts - 52 + 51 apps/client/src/app/pages/features/features-page.html @@ -8256,7 +8296,7 @@ Crypto-monnaies apps/client/src/app/components/markets/markets.component.ts - 53 + 52 apps/client/src/app/pages/features/features-page.html @@ -8276,7 +8316,7 @@ Gérer le profil d’actif apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 466 + 471 @@ -8696,7 +8736,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 26 + 45 diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index 746e1fbd1..b143bbde3 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -51,7 +51,7 @@ Tipo apps/client/src/app/components/admin-jobs/admin-jobs.html - 48 + 57 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -107,7 +107,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -119,7 +119,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -135,7 +135,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 139 + 135 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -179,15 +179,15 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 205 + 201 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 208 + 204 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 211 + 207 libs/ui/src/lib/account-balances/account-balances.component.html @@ -235,11 +235,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -267,11 +267,15 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 libs/ui/src/lib/account-balances/account-balances.component.html @@ -295,7 +299,7 @@ Vuoi davvero eliminare questo account? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 150 + 148 @@ -303,7 +307,7 @@ Elimina i lavori apps/client/src/app/components/admin-jobs/admin-jobs.html - 193 + 202 @@ -311,7 +315,7 @@ Sorgente dei dati apps/client/src/app/components/admin-jobs/admin-jobs.html - 82 + 91 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -323,7 +327,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 155 + 151 libs/ui/src/lib/i18n.ts @@ -335,7 +339,7 @@ Tentativi apps/client/src/app/components/admin-jobs/admin-jobs.html - 120 + 129 @@ -343,7 +347,7 @@ Creato apps/client/src/app/components/admin-jobs/admin-jobs.html - 134 + 143 @@ -351,7 +355,7 @@ Finito apps/client/src/app/components/admin-jobs/admin-jobs.html - 143 + 152 @@ -359,11 +363,11 @@ Stato apps/client/src/app/components/admin-jobs/admin-jobs.html - 152 + 161 apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -379,7 +383,7 @@ Profilo dell’asset apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -391,11 +395,11 @@ Dati storici del mercato apps/client/src/app/components/admin-jobs/admin-jobs.html - 54 + 63 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -403,7 +407,7 @@ Visualizza i dati apps/client/src/app/components/admin-jobs/admin-jobs.html - 208 + 217 @@ -411,7 +415,7 @@ Visualizza Stacktrace apps/client/src/app/components/admin-jobs/admin-jobs.html - 216 + 225 @@ -419,7 +423,7 @@ Elimina il lavoro apps/client/src/app/components/admin-jobs/admin-jobs.html - 224 + 233 @@ -435,7 +439,7 @@ Data apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 161 + 157 libs/ui/src/lib/account-balances/account-balances.component.html @@ -503,7 +507,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 44 + 40 @@ -511,7 +515,7 @@ Vuoi davvero eliminare questo buono? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 193 + 196 @@ -519,7 +523,7 @@ Vuoi davvero svuotare la cache? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 230 + 233 @@ -527,7 +531,7 @@ Imposta il messaggio di sistema: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 250 + 253 @@ -667,7 +671,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 89 + 108 @@ -723,7 +727,7 @@ apps/client/src/app/pages/public/public-page.html - 242 + 241 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -739,7 +743,7 @@ apps/client/src/app/components/header/header.component.ts - 297 + 298 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -759,15 +763,15 @@ Ops! Token di sicurezza errato. apps/client/src/app/components/header/header.component.ts - 312 + 313 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 154 + 152 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 193 + 191 @@ -775,7 +779,7 @@ Gestione delle attività apps/client/src/app/components/home-holdings/home-holdings.html - 67 + 64 @@ -891,7 +895,7 @@ Prestazioni lorde assolute apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 73 + 77 @@ -899,7 +903,7 @@ Prestazioni nette assolute apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 107 + 111 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -911,7 +915,7 @@ Prestazioni nette apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 123 + 127 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -923,7 +927,7 @@ Asset totali apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 149 + 153 @@ -931,7 +935,7 @@ Potere d’acquisto apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 241 + 248 @@ -939,7 +943,7 @@ Patrimonio netto apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 317 + 324 @@ -947,7 +951,7 @@ Prestazioni annualizzate apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 331 + 338 @@ -967,7 +971,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -975,7 +979,7 @@ apps/client/src/app/pages/public/public-page.html - 114 + 113 @@ -987,7 +991,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -999,7 +1003,7 @@ Tag apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1015,7 +1019,7 @@ Segnala un’anomalia dei dati apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 451 + 456 @@ -1043,7 +1047,7 @@ Mostra tutti libs/ui/src/lib/holdings-table/holdings-table.component.html - 216 + 212 @@ -1055,7 +1059,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 363 + 362 @@ -1063,11 +1067,11 @@ anno corrente apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -1075,11 +1079,11 @@ 1 anno apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -1087,11 +1091,11 @@ 5 anni apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -1107,11 +1111,11 @@ Massimo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 217 libs/ui/src/lib/assistant/assistant.component.ts - 415 + 414 @@ -1127,7 +1131,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 196 + 195 @@ -1219,7 +1223,7 @@ Vuoi davvero rimuovere questo metodo di accesso? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 279 @@ -1279,7 +1283,7 @@ Locale apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1327,7 +1331,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 11 + 30 @@ -1375,11 +1379,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 375 + 381 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 66 + 85 apps/client/src/app/pages/accounts/accounts-page.html @@ -1419,7 +1423,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1431,7 +1435,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 145 + 141 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -1611,7 +1615,7 @@ Poiché hai già effettuato l’accesso, non puoi accedere all’account demo. apps/client/src/app/pages/demo/demo-page.component.ts - 35 + 32 @@ -1657,6 +1661,10 @@ Overview Panoramica + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 7 + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 114 @@ -1675,11 +1683,11 @@ apps/client/src/app/pages/admin/admin-page.component.ts - 48 + 45 apps/client/src/app/pages/resources/resources-page.component.ts - 30 + 29 libs/common/src/lib/routes/routes.ts @@ -1695,7 +1703,7 @@ Mercati apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -1719,7 +1727,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 40 + 39 libs/common/src/lib/routes/routes.ts @@ -1819,7 +1827,7 @@ apps/client/src/app/pages/public/public-page.html - 151 + 150 @@ -1915,7 +1923,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 @@ -1959,7 +1967,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 124 + 120 @@ -1971,7 +1979,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 189 + 185 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1987,7 +1995,7 @@ Prezzo unitario apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 214 + 210 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1999,7 +2007,7 @@ Nota apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2007,7 +2015,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 275 + 271 @@ -2019,7 +2027,7 @@ apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 115 + 113 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -2039,11 +2047,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 342 + 348 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 75 + 94 apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -2063,7 +2071,7 @@ Importazione dei dati... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 175 + 174 @@ -2071,7 +2079,7 @@ L’importazione è stata completata apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 185 + 184 @@ -2143,7 +2151,7 @@ Continenti apps/client/src/app/pages/public/public-page.html - 132 + 131 @@ -2159,7 +2167,7 @@ Ghostfolio ti permette di tenere traccia della tua ricchezza. apps/client/src/app/pages/public/public-page.html - 238 + 237 @@ -2189,6 +2197,10 @@ apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 88 + + libs/ui/src/lib/value/value.component.html + 18 + Resources @@ -2307,7 +2319,7 @@ Vuoi davvero eliminare questa attività? libs/ui/src/lib/activities-table/activities-table.component.ts - 288 + 292 @@ -2375,7 +2387,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 193 + 192 @@ -2391,7 +2403,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 195 + 194 @@ -2415,7 +2427,7 @@ apps/client/src/app/pages/public/public-page.html - 168 + 167 @@ -2423,7 +2435,7 @@ Latest activities apps/client/src/app/pages/public/public-page.html - 211 + 210 @@ -2443,7 +2455,7 @@ apps/client/src/app/pages/public/public-page.html - 186 + 185 @@ -2455,7 +2467,7 @@ apps/client/src/app/pages/public/public-page.html - 177 + 176 @@ -2487,7 +2499,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 53 + 72 @@ -2511,7 +2523,7 @@ Risparmio libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 397 + 424 @@ -2523,11 +2535,11 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 352 + 359 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 387 + 414 libs/ui/src/lib/i18n.ts @@ -2547,7 +2559,7 @@ Deposito libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 377 + 404 @@ -2555,7 +2567,7 @@ Mensile apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 91 + 90 @@ -2579,11 +2591,11 @@ Paura apps/client/src/app/components/home-market/home-market.component.ts - 42 + 41 apps/client/src/app/components/markets/markets.component.ts - 47 + 46 libs/ui/src/lib/i18n.ts @@ -2595,11 +2607,11 @@ Avidità apps/client/src/app/components/home-market/home-market.component.ts - 43 + 42 apps/client/src/app/components/markets/markets.component.ts - 48 + 47 libs/ui/src/lib/i18n.ts @@ -2611,7 +2623,7 @@ Filtra per... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 385 + 383 @@ -2647,7 +2659,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2659,11 +2671,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -2687,7 +2699,7 @@ Escluso dall’analisi apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 267 + 274 @@ -2695,7 +2707,7 @@ Auto apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 70 + 69 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2731,7 +2743,7 @@ Importo totale apps/client/src/app/components/investment-chart/investment-chart.component.ts - 143 + 146 @@ -2783,7 +2795,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2791,7 +2803,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 290 + 286 libs/ui/src/lib/i18n.ts @@ -2807,7 +2819,7 @@ Simbolo apps/client/src/app/components/admin-jobs/admin-jobs.html - 68 + 77 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -2823,7 +2835,7 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 315 + 318 libs/ui/src/lib/i18n.ts @@ -2847,7 +2859,7 @@ Contanti apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 212 + 219 libs/ui/src/lib/i18n.ts @@ -2895,7 +2907,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 35 + 54 @@ -2959,7 +2971,7 @@ Fondo di emergenza apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 164 + 168 apps/client/src/app/pages/features/features-page.html @@ -2979,7 +2991,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 437 + 451 @@ -2991,7 +3003,7 @@ apps/client/src/app/pages/public/public-page.html - 196 + 195 libs/ui/src/lib/benchmark/benchmark.component.html @@ -2999,11 +3011,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 439 + 453 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 452 + 467 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3107,7 +3119,7 @@ Mappatura dei simboli apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -3131,7 +3143,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 365 + 372 apps/client/src/app/pages/features/features-page.html @@ -3139,11 +3151,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 202 + 198 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 76 + 75 libs/ui/src/lib/i18n.ts @@ -3171,7 +3183,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3179,7 +3191,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 309 + 305 libs/ui/src/lib/i18n.ts @@ -3199,7 +3211,7 @@ Convalida dei dati... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 299 + 293 @@ -3215,7 +3227,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 71 + 67 @@ -3223,7 +3235,7 @@ Dati del mercato apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 398 + 403 libs/common/src/lib/routes/routes.ts @@ -3240,7 +3252,7 @@ Summary - Summario + Riepilogo apps/client/src/app/components/home-summary/home-summary.html 2 @@ -3275,7 +3287,7 @@ Annuale apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 91 @@ -3283,7 +3295,7 @@ Importa i dividendi apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 137 + 136 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3299,7 +3311,7 @@ Valido fino a apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -3338,6 +3350,14 @@ 22 + + No Activities + No Activities + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 145 + + Retirement Provision Fondo pensione @@ -3611,11 +3631,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -3635,7 +3655,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 88 + 92 @@ -3799,7 +3819,7 @@ Vuoi davvero eliminare tutte le tue attività? libs/ui/src/lib/activities-table/activities-table.component.ts - 278 + 282 @@ -3810,6 +3830,14 @@ 306 + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 + + By By @@ -3831,7 +3859,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 @@ -3847,11 +3875,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3867,7 +3895,7 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -3875,7 +3903,7 @@ Vuoi davvero eliminare questa piattaforma? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 111 @@ -3883,7 +3911,7 @@ Piattaforme apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -3891,7 +3919,7 @@ Aggiornamento del saldo di cassa apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 112 + 108 @@ -4071,7 +4099,7 @@ Passività apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 295 + 302 apps/client/src/app/pages/features/features-page.html @@ -4227,7 +4255,7 @@ Configurazione dello scraper apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -4471,7 +4499,7 @@ ETF senza paesi apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 136 + 135 @@ -4479,7 +4507,7 @@ ETF senza settori apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 141 + 140 @@ -4487,7 +4515,7 @@ Asset apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 226 + 233 @@ -4659,7 +4687,7 @@ Job ID apps/client/src/app/components/admin-jobs/admin-jobs.html - 34 + 43 @@ -4715,11 +4743,11 @@ Valute apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 131 + 130 apps/client/src/app/pages/public/public-page.html - 96 + 95 @@ -4767,11 +4795,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -5433,7 +5461,7 @@ Commissione apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 262 + 258 libs/ui/src/lib/activities-table/activities-table.component.html @@ -5465,7 +5493,7 @@ Sei sicuro di voler eliminare questo tag? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 108 @@ -5533,7 +5561,7 @@ Iscrizione apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 48 + 67 libs/common/src/lib/routes/routes.ts @@ -5565,7 +5593,7 @@ Profilo dell’asset apps/client/src/app/components/admin-jobs/admin-jobs.html - 52 + 61 @@ -5689,7 +5717,7 @@ Ops! Impossibile elaborare i dati storici. libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts - 262 + 284 @@ -5697,7 +5725,7 @@ Confermi di voler cancellare questo messaggio di sistema? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 206 + 209 @@ -5721,7 +5749,7 @@ Saldi di cassa apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 148 + 146 @@ -5741,7 +5769,7 @@ Vuoi veramente elimnare il saldo di questo conto? libs/ui/src/lib/account-balances/account-balances.component.ts - 120 + 113 @@ -5757,7 +5785,7 @@ L’attuale prezzo di mercato è apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5765,7 +5793,7 @@ Prova apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5853,11 +5881,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 81 + 80 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 97 + 96 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5877,7 +5905,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 447 @@ -5917,7 +5945,7 @@ Da inizio settimana libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5925,11 +5953,11 @@ Settimana corrente apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5937,7 +5965,7 @@ Da inizio mese libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5945,11 +5973,11 @@ Mese corrente apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5957,7 +5985,7 @@ Da inizio anno libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -5993,7 +6021,7 @@ anno apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6005,7 +6033,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -6013,11 +6041,11 @@ anni apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -6033,7 +6061,7 @@ Raccolta Dati apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6045,7 +6073,7 @@ Generale apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6053,7 +6081,7 @@ Cloud apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6065,7 +6093,7 @@ Self-Hosting apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6106,7 +6134,7 @@ Attivo apps/client/src/app/components/home-holdings/home-holdings.component.ts - 64 + 63 @@ -6114,7 +6142,7 @@ Chiuso apps/client/src/app/components/home-holdings/home-holdings.component.ts - 65 + 64 @@ -6146,7 +6174,7 @@ Esegui il lavoro apps/client/src/app/components/admin-jobs/admin-jobs.html - 220 + 229 @@ -6154,7 +6182,7 @@ Priorità apps/client/src/app/components/admin-jobs/admin-jobs.html - 96 + 105 @@ -6210,7 +6238,7 @@ Confermi di voler chiudere il tuo account Ghostfolio? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 205 @@ -6258,7 +6286,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6266,7 +6294,7 @@ Ops! C’è stato un errore impostando l’autenticazione biometrica. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 336 + 333 @@ -6282,7 +6310,7 @@ Benchmarks apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 126 + 125 @@ -6330,7 +6358,7 @@ Vorresti perfezionare la tua strategia personale di investimento? apps/client/src/app/pages/public/public-page.html - 234 + 233 @@ -6662,7 +6690,7 @@ Errore apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6714,7 +6742,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6746,7 +6774,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 345 + 341 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -6754,7 +6782,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 46 + 47 libs/ui/src/lib/i18n.ts @@ -6766,7 +6794,7 @@ Chiudi apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6792,13 +6820,17 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 68 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 127 + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 107 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 347 + 343 libs/ui/src/lib/i18n.ts @@ -6810,7 +6842,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 14 + 33 @@ -6834,7 +6866,7 @@ Stato del Portfolio apps/client/src/app/components/admin-jobs/admin-jobs.html - 56 + 65 @@ -6901,6 +6933,14 @@ 42 + + has been copied to the clipboard + has been copied to the clipboard + + libs/ui/src/lib/value/value.component.ts + 180 + + From the beginning Dall’inizio @@ -7066,7 +7106,7 @@ Imposta API Key apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7102,7 +7142,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 45 + 44 libs/common/src/lib/routes/routes.ts @@ -7118,7 +7158,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 34 + 33 libs/common/src/lib/routes/routes.ts @@ -7172,7 +7212,7 @@ di apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7180,7 +7220,7 @@ richieste giornaliere apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7188,7 +7228,7 @@ Rimuovi API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7196,7 +7236,7 @@ Vuoi davvero eliminare l’API key? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7216,7 +7256,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 98 + 117 @@ -7288,7 +7328,7 @@ Salva apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7324,11 +7364,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 356 + 352 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 48 + 49 @@ -7340,7 +7380,7 @@ apps/client/src/app/components/user-account-access/user-account-access.component.ts - 260 + 257 @@ -7364,7 +7404,7 @@ Inserisci la tua API key di Ghostfolio. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7380,7 +7420,7 @@ L’AI prompt è stato copiato negli appunti apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 201 + 199 @@ -7396,7 +7436,7 @@ Pigro apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7404,7 +7444,7 @@ Istantaneo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7412,7 +7452,7 @@ Prezzo di mercato predefinito apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7420,7 +7460,7 @@ Modalità apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7428,7 +7468,7 @@ Selettore apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7436,7 +7476,7 @@ Intestazioni della richiesta HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7444,7 +7484,7 @@ fine giornata apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7452,7 +7492,7 @@ in tempo reale apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7460,7 +7500,7 @@ Apri Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 202 + 200 @@ -7480,7 +7520,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 @@ -7492,7 +7532,7 @@ apps/client/src/app/components/home-overview/home-overview.component.ts - 55 + 54 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -7500,11 +7540,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 381 + 390 @@ -7608,11 +7648,11 @@ Token di sicurezza apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 235 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 170 + 167 @@ -7620,7 +7660,7 @@ Vuoi davvero generare un nuovo token di sicurezza per questo utente? apps/client/src/app/components/admin-users/admin-users.component.ts - 241 + 240 @@ -7628,7 +7668,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 151 + 115 @@ -7693,7 +7733,7 @@ () e gia in uso. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7701,7 +7741,7 @@ Si è verificato un errore durante l’aggiornamento di (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7765,7 +7805,7 @@ qualcuno apps/client/src/app/pages/public/public-page.component.ts - 59 + 61 @@ -7797,7 +7837,7 @@ Vuoi davvero eliminare questo elemento? libs/ui/src/lib/benchmark/benchmark.component.ts - 144 + 139 @@ -7838,7 +7878,7 @@ L’account utente demo è stato sincronizzato. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 274 + 277 @@ -7873,25 +7913,25 @@ 150 - + Fee Ratio - Rapporto tariffario + Fee Ratio apps/client/src/app/pages/i18n/i18n-page.html 152 - - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - Le commissioni superano il ${thresholdMax}% del tuo investimento iniziale (${feeRatio}%) + + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 - - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - Le commissioni non superano il ${thresholdMax}% del tuo investimento iniziale (${feeRatio}%) + + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -8052,7 +8092,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 @@ -8060,11 +8100,11 @@ nuovo apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts - 56 + 53 @@ -8221,7 +8261,7 @@ Vuoi davvero generare un nuovo token di sicurezza? apps/client/src/app/components/user-account-access/user-account-access.component.ts - 175 + 172 @@ -8245,7 +8285,7 @@ Azioni apps/client/src/app/components/markets/markets.component.ts - 52 + 51 apps/client/src/app/pages/features/features-page.html @@ -8257,7 +8297,7 @@ criptovalute apps/client/src/app/components/markets/markets.component.ts - 53 + 52 apps/client/src/app/pages/features/features-page.html @@ -8277,7 +8317,7 @@ Gestisci profilo risorsa apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 466 + 471 @@ -8697,7 +8737,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 26 + 45 diff --git a/apps/client/src/locales/messages.ko.xlf b/apps/client/src/locales/messages.ko.xlf index 67443706b..4f544e4aa 100644 --- a/apps/client/src/locales/messages.ko.xlf +++ b/apps/client/src/locales/messages.ko.xlf @@ -252,7 +252,7 @@ 유형 apps/client/src/app/components/admin-jobs/admin-jobs.html - 48 + 57 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -336,7 +336,7 @@ 현금 잔액 apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 148 + 146 @@ -360,7 +360,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -372,7 +372,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -388,7 +388,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 139 + 135 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -432,7 +432,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -444,7 +444,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 145 + 141 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -464,15 +464,15 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 205 + 201 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 208 + 204 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 211 + 207 libs/ui/src/lib/account-balances/account-balances.component.html @@ -520,11 +520,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -552,11 +552,15 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 libs/ui/src/lib/account-balances/account-balances.component.html @@ -580,7 +584,7 @@ 이 계정을 정말 삭제하시겠습니까? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 150 + 148 @@ -588,7 +592,7 @@ 자산 프로필 apps/client/src/app/components/admin-jobs/admin-jobs.html - 52 + 61 @@ -596,11 +600,11 @@ 과거 시장 데이터 apps/client/src/app/components/admin-jobs/admin-jobs.html - 54 + 63 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -608,7 +612,7 @@ 데이터 소스 apps/client/src/app/components/admin-jobs/admin-jobs.html - 82 + 91 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -620,7 +624,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 155 + 151 libs/ui/src/lib/i18n.ts @@ -632,7 +636,7 @@ 시도 횟수 apps/client/src/app/components/admin-jobs/admin-jobs.html - 120 + 129 @@ -640,7 +644,7 @@ 생성됨 apps/client/src/app/components/admin-jobs/admin-jobs.html - 134 + 143 @@ -648,7 +652,7 @@ 완료됨 apps/client/src/app/components/admin-jobs/admin-jobs.html - 143 + 152 @@ -656,11 +660,11 @@ 상태 apps/client/src/app/components/admin-jobs/admin-jobs.html - 152 + 161 apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -676,7 +680,7 @@ 작업 삭제 apps/client/src/app/components/admin-jobs/admin-jobs.html - 193 + 202 @@ -684,7 +688,7 @@ 데이터 보기 apps/client/src/app/components/admin-jobs/admin-jobs.html - 208 + 217 @@ -692,7 +696,7 @@ 스택트레이스 보기 apps/client/src/app/components/admin-jobs/admin-jobs.html - 216 + 225 @@ -700,7 +704,7 @@ 작업 삭제 apps/client/src/app/components/admin-jobs/admin-jobs.html - 224 + 233 @@ -716,7 +720,7 @@ 날짜 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 161 + 157 libs/ui/src/lib/account-balances/account-balances.component.html @@ -752,11 +756,11 @@ 통화 apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 131 + 130 apps/client/src/app/pages/public/public-page.html - 96 + 95 @@ -764,7 +768,7 @@ 국가 정보 없는 ETF apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 136 + 135 @@ -772,7 +776,7 @@ 섹터 정보 없는 ETF apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 141 + 140 @@ -788,7 +792,7 @@ 다음 기준으로 필터... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 385 + 383 @@ -828,7 +832,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 44 + 40 @@ -880,7 +884,7 @@ 이런! 과거 데이터를 파싱할 수 없습니다. libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts - 262 + 284 @@ -912,7 +916,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 71 + 67 @@ -944,7 +948,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 53 + 72 @@ -956,7 +960,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -964,7 +968,7 @@ apps/client/src/app/pages/public/public-page.html - 114 + 113 @@ -976,7 +980,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -988,7 +992,7 @@ 심볼 매핑 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -1004,7 +1008,7 @@ 스크래퍼 설정 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -1012,7 +1016,7 @@ 메모 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1020,7 +1024,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 275 + 271 @@ -1064,7 +1068,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 124 + 120 @@ -1072,7 +1076,7 @@ 이 쿠폰을 정말 삭제하시겠습니까? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 193 + 196 @@ -1080,7 +1084,7 @@ 이 시스템 메시지를 정말 삭제하시겠습니까? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 206 + 209 @@ -1088,7 +1092,7 @@ 정말로 캐시를 플러시하시겠습니까? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 230 + 233 @@ -1096,7 +1100,7 @@ 시스템 메시지를 설정하십시오: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 250 + 253 @@ -1220,11 +1224,11 @@ 링크 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1240,7 +1244,7 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -1248,7 +1252,15 @@ 정말로 이 플랫폼을 삭제하시겠습니까? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 111 + + + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 @@ -1272,7 +1284,7 @@ 올해 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 @@ -1288,7 +1300,7 @@ 플랫폼 apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -1296,7 +1308,7 @@ 태그 apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1320,7 +1332,7 @@ 이 태그를 정말로 삭제하시겠습니까? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 108 @@ -1380,7 +1392,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 89 + 108 @@ -1412,11 +1424,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -1464,7 +1476,7 @@ 기준 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1500,7 +1512,7 @@ apps/client/src/app/components/header/header.component.ts - 297 + 298 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -1520,15 +1532,15 @@ 이런! 잘못된 보안 토큰. apps/client/src/app/components/header/header.component.ts - 312 + 313 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 154 + 152 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 193 + 191 @@ -1536,7 +1548,7 @@ 활동 관리 apps/client/src/app/components/home-holdings/home-holdings.html - 67 + 64 @@ -1544,11 +1556,11 @@ 두려움 apps/client/src/app/components/home-market/home-market.component.ts - 42 + 41 apps/client/src/app/components/markets/markets.component.ts - 47 + 46 libs/ui/src/lib/i18n.ts @@ -1560,11 +1572,11 @@ 탐욕 apps/client/src/app/components/home-market/home-market.component.ts - 43 + 42 apps/client/src/app/components/markets/markets.component.ts - 48 + 47 libs/ui/src/lib/i18n.ts @@ -1668,7 +1680,7 @@ 이번주 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 @@ -1688,7 +1700,7 @@ 총액 apps/client/src/app/components/investment-chart/investment-chart.component.ts - 143 + 146 @@ -1800,7 +1812,7 @@ 절대 총 성과 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 73 + 77 @@ -1812,7 +1824,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 88 + 92 @@ -1820,7 +1832,7 @@ 절대 순 성과 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 107 + 111 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -1832,7 +1844,7 @@ 순 성과 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 123 + 127 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -1844,7 +1856,7 @@ 총자산 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 149 + 153 @@ -1852,7 +1864,7 @@ 자산 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 226 + 233 @@ -1860,7 +1872,7 @@ 매수 가능 금액 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 241 + 248 @@ -1868,7 +1880,7 @@ 분석에서 제외됨 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 267 + 274 @@ -1876,7 +1888,7 @@ 부채 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 295 + 302 apps/client/src/app/pages/features/features-page.html @@ -1888,7 +1900,7 @@ 순자산 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 317 + 324 @@ -1896,7 +1908,7 @@ 연환산 성과 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 331 + 338 @@ -1932,7 +1944,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 189 + 185 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1948,7 +1960,7 @@ 데이터 결함 보고 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 451 + 456 @@ -2128,7 +2140,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 363 + 362 @@ -2136,11 +2148,11 @@ 연초 대비 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -2148,11 +2160,11 @@ 1년 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -2160,11 +2172,11 @@ 5년 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -2180,11 +2192,11 @@ 맥스 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 217 libs/ui/src/lib/assistant/assistant.component.ts - 415 + 414 @@ -2284,7 +2296,7 @@ 자동 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 70 + 69 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2296,7 +2308,7 @@ 이 로그인 방법을 정말로 제거하시겠습니까? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 279 @@ -2336,7 +2348,7 @@ 장소 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2448,7 +2460,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 11 + 30 @@ -2480,7 +2492,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 195 + 194 @@ -2492,7 +2504,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 193 + 192 @@ -2508,7 +2520,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 196 + 195 @@ -2636,11 +2648,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 375 + 381 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 66 + 85 apps/client/src/app/pages/accounts/accounts-page.html @@ -2732,7 +2744,7 @@ 시장 데이터 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 398 + 403 libs/common/src/lib/routes/routes.ts @@ -2766,6 +2778,10 @@ Overview 개요 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 7 + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 114 @@ -2784,11 +2800,11 @@ apps/client/src/app/pages/admin/admin-page.component.ts - 48 + 45 apps/client/src/app/pages/resources/resources-page.component.ts - 30 + 29 libs/common/src/lib/routes/routes.ts @@ -2912,11 +2928,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -2932,7 +2948,7 @@ 이미 로그인되어 있으므로 데모 계정에 접근할 수 없습니다. apps/client/src/app/pages/demo/demo-page.component.ts - 35 + 32 @@ -3104,7 +3120,7 @@ apps/client/src/app/pages/public/public-page.html - 242 + 241 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -3156,7 +3172,7 @@ 시장 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -3180,7 +3196,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 40 + 39 libs/common/src/lib/routes/routes.ts @@ -3612,7 +3628,7 @@ 작업 ID apps/client/src/app/components/admin-jobs/admin-jobs.html - 34 + 43 @@ -3648,7 +3664,7 @@ apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 115 + 113 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -3668,11 +3684,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 342 + 348 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 75 + 94 apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -3692,7 +3708,7 @@ 정말로 이 활동을 삭제하시겠습니까? libs/ui/src/lib/activities-table/activities-table.component.ts - 278 + 282 @@ -3760,7 +3776,7 @@ 현금 잔액 업데이트 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 112 + 108 @@ -3768,7 +3784,7 @@ 단가 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 214 + 210 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3796,7 +3812,7 @@ 배당금 가져오기 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 137 + 136 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3812,7 +3828,7 @@ 데이터 가져오는 중... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 175 + 174 @@ -3820,7 +3836,7 @@ 가져오기가 완료되었습니다. apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 185 + 184 @@ -3836,7 +3852,7 @@ 데이터 유효성을 검사하는 중... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 299 + 293 @@ -4008,7 +4024,7 @@ apps/client/src/app/pages/public/public-page.html - 151 + 150 @@ -4032,7 +4048,7 @@ apps/client/src/app/pages/public/public-page.html - 168 + 167 @@ -4040,7 +4056,7 @@ 최신 활동 apps/client/src/app/pages/public/public-page.html - 211 + 210 @@ -4052,7 +4068,7 @@ apps/client/src/app/pages/public/public-page.html - 177 + 176 @@ -4064,7 +4080,7 @@ apps/client/src/app/pages/public/public-page.html - 186 + 185 @@ -4124,7 +4140,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 365 + 372 apps/client/src/app/pages/features/features-page.html @@ -4132,11 +4148,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 202 + 198 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 76 + 75 libs/ui/src/lib/i18n.ts @@ -4156,7 +4172,7 @@ 보증금 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 377 + 404 @@ -4164,7 +4180,7 @@ 월간 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 91 + 90 @@ -4172,7 +4188,7 @@ 매년 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 91 @@ -4452,11 +4468,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -4480,7 +4496,7 @@ 대륙 apps/client/src/app/pages/public/public-page.html - 132 + 131 @@ -4496,7 +4512,7 @@ Ghostfolio는 귀하의 재산을 추적할 수 있도록 해줍니다. apps/client/src/app/pages/public/public-page.html - 238 + 237 @@ -4526,6 +4542,10 @@ apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 88 + + libs/ui/src/lib/value/value.component.html + 18 + Personal Finance Tools @@ -4869,7 +4889,7 @@ 멤버십 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 48 + 67 libs/common/src/lib/routes/routes.ts @@ -4937,7 +4957,7 @@ 정말로 이 계정 잔액을 삭제하시겠습니까? libs/ui/src/lib/account-balances/account-balances.component.ts - 120 + 113 @@ -4993,7 +5013,7 @@ 정말로 이 활동을 삭제하시겠습니까? libs/ui/src/lib/activities-table/activities-table.component.ts - 288 + 292 @@ -5001,7 +5021,7 @@ 자산 프로필 apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -5113,11 +5133,11 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 352 + 359 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 387 + 414 libs/ui/src/lib/i18n.ts @@ -5129,7 +5149,7 @@ 저금 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 397 + 424 @@ -5157,7 +5177,7 @@ 모두 표시 libs/ui/src/lib/holdings-table/holdings-table.component.html - 216 + 212 @@ -5201,7 +5221,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5209,7 +5229,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 290 + 286 libs/ui/src/lib/i18n.ts @@ -5233,7 +5253,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5241,7 +5261,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 309 + 305 libs/ui/src/lib/i18n.ts @@ -5285,7 +5305,7 @@ 비상자금 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 164 + 168 apps/client/src/app/pages/features/features-page.html @@ -5361,7 +5381,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 437 + 451 @@ -5372,6 +5392,14 @@ 27 + + No Activities + No Activities + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 145 + + Retirement Provision 퇴직금 @@ -5401,7 +5429,7 @@ 상징 apps/client/src/app/components/admin-jobs/admin-jobs.html - 68 + 77 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -5417,7 +5445,7 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 315 + 318 libs/ui/src/lib/i18n.ts @@ -5489,7 +5517,7 @@ 요금 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 262 + 258 libs/ui/src/lib/activities-table/activities-table.component.html @@ -5533,7 +5561,7 @@ 현금 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 212 + 219 libs/ui/src/lib/i18n.ts @@ -5581,7 +5609,7 @@ 입증 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 35 + 54 @@ -5725,7 +5753,7 @@ 유효기간 apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -5749,7 +5777,7 @@ apps/client/src/app/pages/public/public-page.html - 196 + 195 libs/ui/src/lib/benchmark/benchmark.component.html @@ -5757,11 +5785,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 439 + 453 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 452 + 467 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -5789,7 +5817,7 @@ 현재 시장가격은 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5797,7 +5825,7 @@ 시험 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5877,7 +5905,7 @@ 닫기 보유 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 447 @@ -5901,11 +5929,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 81 + 80 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 97 + 96 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5941,7 +5969,7 @@ 연초 현재 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -5949,7 +5977,7 @@ 이번주 현재까지 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5957,7 +5985,7 @@ 월간 누계 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5965,11 +5993,11 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5977,11 +6005,11 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -6017,7 +6045,7 @@ 년도 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6029,7 +6057,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -6037,11 +6065,11 @@ 연령 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -6070,7 +6098,7 @@ 셀프 호스팅 apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6082,7 +6110,7 @@ 데이터 수집 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6094,7 +6122,7 @@ 일반적인 apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6102,7 +6130,7 @@ 구름 apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6130,7 +6158,7 @@ 닫은 apps/client/src/app/components/home-holdings/home-holdings.component.ts - 65 + 64 @@ -6138,7 +6166,7 @@ 활동적인 apps/client/src/app/components/home-holdings/home-holdings.component.ts - 64 + 63 @@ -6170,7 +6198,7 @@ 작업 실행 apps/client/src/app/components/admin-jobs/admin-jobs.html - 220 + 229 @@ -6186,7 +6214,7 @@ 우선 사항 apps/client/src/app/components/admin-jobs/admin-jobs.html - 96 + 105 @@ -6242,7 +6270,7 @@ 정말로 Ghostfolio 계정을 폐쇄하시겠습니까? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 205 @@ -6282,7 +6310,7 @@ 포함 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6290,7 +6318,7 @@ 이런! 생체 인증을 설정하는 중에 오류가 발생했습니다. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 336 + 333 @@ -6330,7 +6358,7 @@ 벤치마크 apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 126 + 125 @@ -6354,7 +6382,7 @@ 개인 투자 전략개선하시겠습니까? apps/client/src/app/pages/public/public-page.html - 234 + 233 @@ -6686,7 +6714,7 @@ 오류 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6698,7 +6726,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6730,7 +6758,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 345 + 341 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -6738,7 +6766,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 46 + 47 libs/ui/src/lib/i18n.ts @@ -6750,7 +6778,7 @@ 역할 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 14 + 33 @@ -6782,7 +6810,7 @@ 닫다 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6808,13 +6836,17 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 68 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 127 + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 107 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 347 + 343 libs/ui/src/lib/i18n.ts @@ -6874,7 +6906,7 @@ 포트폴리오 스냅샷 apps/client/src/app/components/admin-jobs/admin-jobs.html - 56 + 65 @@ -6933,6 +6965,14 @@ 42 + + has been copied to the clipboard + has been copied to the clipboard + + libs/ui/src/lib/value/value.component.ts + 180 + + offers a free plan 은(는) 무료 요금제를 제공합니다 @@ -7090,7 +7130,7 @@ API 키 설정 apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7126,7 +7166,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 45 + 44 libs/common/src/lib/routes/routes.ts @@ -7155,7 +7195,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 34 + 33 libs/common/src/lib/routes/routes.ts @@ -7204,7 +7244,7 @@ ~의 apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7212,7 +7252,7 @@ API 키를 정말로 삭제하시겠습니까? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7220,7 +7260,7 @@ API 키 제거 apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7228,7 +7268,7 @@ 일일 요청 apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7256,7 +7296,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 98 + 117 @@ -7312,7 +7352,7 @@ 구하다 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7348,11 +7388,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 356 + 352 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 48 + 49 @@ -7380,7 +7420,7 @@ apps/client/src/app/components/user-account-access/user-account-access.component.ts - 260 + 257 @@ -7388,7 +7428,7 @@ Ghostfolio API 키를 입력하세요. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7404,7 +7444,7 @@ AI 프롬프트가 클립보드에 복사되었습니다. apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 201 + 199 @@ -7420,7 +7460,7 @@ 방법 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7428,7 +7468,7 @@ 기본 시장 가격 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7436,7 +7476,7 @@ 선택자 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7444,7 +7484,7 @@ 즉각적인 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7452,7 +7492,7 @@ 게으른 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7460,7 +7500,7 @@ HTTP 요청 헤더 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7468,7 +7508,7 @@ 실시간 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7476,7 +7516,7 @@ 하루의 끝 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7484,7 +7524,7 @@ 오픈 Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 202 + 200 @@ -7504,7 +7544,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 @@ -7516,7 +7556,7 @@ apps/client/src/app/components/home-overview/home-overview.component.ts - 55 + 54 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -7524,11 +7564,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 381 + 390 @@ -7632,7 +7672,7 @@ 정말로 이 사용자에 대한 새 보안 토큰을 생성하시겠습니까? apps/client/src/app/components/admin-users/admin-users.component.ts - 241 + 240 @@ -7640,7 +7680,7 @@ 계정, 보유 또는 페이지 찾기... libs/ui/src/lib/assistant/assistant.component.ts - 151 + 115 @@ -7648,11 +7688,11 @@ 보안 토큰 apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 235 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 170 + 167 @@ -7717,7 +7757,7 @@ ()은(는) 이미 사용 중입니다. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7725,7 +7765,7 @@ ()로 업데이트하는 동안 오류가 발생했습니다. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7765,7 +7805,7 @@ 누구 apps/client/src/app/pages/public/public-page.component.ts - 59 + 61 @@ -7797,7 +7837,7 @@ 이 항목을 정말로 삭제하시겠습니까? libs/ui/src/lib/benchmark/benchmark.component.ts - 144 + 139 @@ -7846,7 +7886,7 @@ 데모 사용자 계정이 동기화되었습니다. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 274 + 277 @@ -7873,25 +7913,25 @@ 150 - + Fee Ratio - 수수료 비율 + Fee Ratio apps/client/src/app/pages/i18n/i18n-page.html 152 - - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - 수수료가 초기 투자금(${feeRatio}%)의 ${thresholdMax}%를 초과합니다. + + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 - - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - 수수료는 초기 투자금의 ${thresholdMax}%(${feeRatio}%)를 초과하지 않습니다. + + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -8052,7 +8092,7 @@ 이번 달 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 @@ -8060,11 +8100,11 @@ 새로운 apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts - 56 + 53 @@ -8237,7 +8277,7 @@ 정말로 새로운 보안 토큰을 생성하시겠습니까? apps/client/src/app/components/user-account-access/user-account-access.component.ts - 175 + 172 @@ -8245,7 +8285,7 @@ 암호화폐 apps/client/src/app/components/markets/markets.component.ts - 53 + 52 apps/client/src/app/pages/features/features-page.html @@ -8257,7 +8297,7 @@ 주식 apps/client/src/app/components/markets/markets.component.ts - 52 + 51 apps/client/src/app/pages/features/features-page.html @@ -8277,7 +8317,7 @@ 자산 프로필 관리 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 466 + 471 @@ -8697,7 +8737,7 @@ 등록일 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 26 + 45 diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index d7d0b71e8..039969da0 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -39,7 +39,7 @@ please - please + alsjeblieft apps/client/src/app/pages/pricing/pricing-page.html 333 @@ -50,7 +50,7 @@ Type apps/client/src/app/components/admin-jobs/admin-jobs.html - 48 + 57 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -83,7 +83,7 @@ with - with + met apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 87 @@ -106,7 +106,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -118,7 +118,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -134,7 +134,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 139 + 135 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -178,15 +178,15 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 205 + 201 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 208 + 204 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 211 + 207 libs/ui/src/lib/account-balances/account-balances.component.html @@ -234,11 +234,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -266,11 +266,15 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 libs/ui/src/lib/account-balances/account-balances.component.html @@ -294,7 +298,7 @@ Wil je deze rekening echt verwijderen? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 150 + 148 @@ -302,7 +306,7 @@ Taken verwijderen apps/client/src/app/components/admin-jobs/admin-jobs.html - 193 + 202 @@ -310,7 +314,7 @@ Gegevensbron apps/client/src/app/components/admin-jobs/admin-jobs.html - 82 + 91 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -322,7 +326,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 155 + 151 libs/ui/src/lib/i18n.ts @@ -334,7 +338,7 @@ Pogingen apps/client/src/app/components/admin-jobs/admin-jobs.html - 120 + 129 @@ -342,7 +346,7 @@ Aangemaakt apps/client/src/app/components/admin-jobs/admin-jobs.html - 134 + 143 @@ -350,7 +354,7 @@ Voltooid apps/client/src/app/components/admin-jobs/admin-jobs.html - 143 + 152 @@ -358,16 +362,16 @@ Status apps/client/src/app/components/admin-jobs/admin-jobs.html - 152 + 161 apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 and is driven by the efforts of its contributors - and is driven by the efforts of its contributors + en wordt gedreven door de inspanningen van zijn bijdragers apps/client/src/app/pages/about/overview/about-overview-page.html 49 @@ -378,7 +382,7 @@ Activa Profiel apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -390,11 +394,11 @@ Historische marktgegevens apps/client/src/app/components/admin-jobs/admin-jobs.html - 54 + 63 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -402,7 +406,7 @@ Bekijk gegevens apps/client/src/app/components/admin-jobs/admin-jobs.html - 208 + 217 @@ -410,7 +414,7 @@ Bekijk Stacktrace apps/client/src/app/components/admin-jobs/admin-jobs.html - 216 + 225 @@ -418,7 +422,7 @@ Taak verwijderen apps/client/src/app/components/admin-jobs/admin-jobs.html - 224 + 233 @@ -434,7 +438,7 @@ Datum apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 161 + 157 libs/ui/src/lib/account-balances/account-balances.component.html @@ -502,7 +506,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 44 + 40 @@ -510,7 +514,7 @@ Wil je deze coupon echt verwijderen? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 193 + 196 @@ -518,7 +522,7 @@ Wil je echt de cache legen? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 230 + 233 @@ -526,7 +530,7 @@ Stel je systeemboodschap in: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 250 + 253 @@ -651,7 +655,7 @@ No auto-renewal on membership. - No auto-renewal on membership. + Het lidmaatschap wordt niet automatisch verlengd. apps/client/src/app/components/user-account-membership/user-account-membership.html 74 @@ -666,7 +670,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 89 + 108 @@ -722,7 +726,7 @@ apps/client/src/app/pages/public/public-page.html - 242 + 241 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -738,7 +742,7 @@ apps/client/src/app/components/header/header.component.ts - 297 + 298 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -758,15 +762,15 @@ Oeps! Onjuiste beveiligingstoken. apps/client/src/app/components/header/header.component.ts - 312 + 313 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 154 + 152 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 193 + 191 @@ -774,7 +778,7 @@ Activiteiten beheren apps/client/src/app/components/home-holdings/home-holdings.html - 67 + 64 @@ -890,7 +894,7 @@ Absoluut bruto rendement apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 73 + 77 @@ -898,7 +902,7 @@ Absoluut netto rendement apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 107 + 111 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -910,7 +914,7 @@ Netto rendement apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 123 + 127 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -922,7 +926,7 @@ Totaal Activa apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 149 + 153 @@ -930,7 +934,7 @@ Koopkracht apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 241 + 248 @@ -938,7 +942,7 @@ Netto waarde apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 317 + 324 @@ -946,7 +950,7 @@ Rendement per jaar apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 331 + 338 @@ -966,7 +970,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -974,7 +978,7 @@ apps/client/src/app/pages/public/public-page.html - 114 + 113 @@ -986,7 +990,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -998,7 +1002,7 @@ Tags apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1014,7 +1018,7 @@ Gegevensstoring melden apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 451 + 456 @@ -1042,7 +1046,7 @@ Toon alle libs/ui/src/lib/holdings-table/holdings-table.component.html - 216 + 212 @@ -1054,7 +1058,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 363 + 362 @@ -1062,11 +1066,11 @@ YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -1074,11 +1078,11 @@ 1J apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -1086,16 +1090,16 @@ 5J apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 Performance with currency effect - Performance with currency effect + Prestaties met valuta-effect apps/client/src/app/pages/portfolio/analysis/analysis-page.html 135 @@ -1106,11 +1110,11 @@ Max apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 217 libs/ui/src/lib/assistant/assistant.component.ts - 415 + 414 @@ -1126,7 +1130,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 196 + 195 @@ -1218,7 +1222,7 @@ Wil je deze aanmeldingsmethode echt verwijderen? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 279 @@ -1278,7 +1282,7 @@ Locatie apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1326,7 +1330,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 11 + 30 @@ -1374,11 +1378,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 375 + 381 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 66 + 85 apps/client/src/app/pages/accounts/accounts-page.html @@ -1418,7 +1422,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -1430,7 +1434,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 145 + 141 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -1610,7 +1614,7 @@ Aangezien je al ingelogd bent, heb je geen toegang tot de demo-account. apps/client/src/app/pages/demo/demo-page.component.ts - 35 + 32 @@ -1656,6 +1660,10 @@ Overview Overzicht + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 7 + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 114 @@ -1674,11 +1682,11 @@ apps/client/src/app/pages/admin/admin-page.component.ts - 48 + 45 apps/client/src/app/pages/resources/resources-page.component.ts - 30 + 29 libs/common/src/lib/routes/routes.ts @@ -1694,7 +1702,7 @@ Markten apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -1718,7 +1726,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 40 + 39 libs/common/src/lib/routes/routes.ts @@ -1818,7 +1826,7 @@ apps/client/src/app/pages/public/public-page.html - 151 + 150 @@ -1911,10 +1919,10 @@ Current week - Current week + Huidige week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 @@ -1958,7 +1966,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 124 + 120 @@ -1970,7 +1978,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 189 + 185 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1986,7 +1994,7 @@ Prijs per eenheid apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 214 + 210 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1998,7 +2006,7 @@ Opmerking apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2006,7 +2014,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 275 + 271 @@ -2018,7 +2026,7 @@ apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 115 + 113 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -2038,11 +2046,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 342 + 348 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 75 + 94 apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -2062,7 +2070,7 @@ Gegevens importeren... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 175 + 174 @@ -2070,12 +2078,12 @@ Importeren is voltooid apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 185 + 184 or start a discussion at - or start a discussion at + of start een discussie op apps/client/src/app/pages/about/overview/about-overview-page.html 94 @@ -2142,12 +2150,12 @@ Continenten apps/client/src/app/pages/public/public-page.html - 132 + 131 Sustainable retirement income - Sustainable retirement income + Duurzaam pensioeninkomen apps/client/src/app/pages/portfolio/fire/fire-page.html 41 @@ -2158,7 +2166,7 @@ Ghostfolio stelt je in staat om je vermogen bij te houden. apps/client/src/app/pages/public/public-page.html - 238 + 237 @@ -2188,6 +2196,10 @@ apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 88 + + libs/ui/src/lib/value/value.component.html + 18 + Resources @@ -2306,7 +2318,7 @@ Wil je deze activiteit echt verwijderen? libs/ui/src/lib/activities-table/activities-table.component.ts - 288 + 292 @@ -2319,7 +2331,7 @@ contact us - contact us + contacteer ons apps/client/src/app/pages/pricing/pricing-page.html 336 @@ -2374,7 +2386,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 193 + 192 @@ -2390,12 +2402,12 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 195 + 194 Exclude from Analysis - Exclude from Analysis + Uitsluiten van analyse apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 90 @@ -2414,15 +2426,15 @@ apps/client/src/app/pages/public/public-page.html - 168 + 167 Latest activities - Latest activities + Laatste activiteiten apps/client/src/app/pages/public/public-page.html - 211 + 210 @@ -2442,7 +2454,7 @@ apps/client/src/app/pages/public/public-page.html - 186 + 185 @@ -2454,7 +2466,7 @@ apps/client/src/app/pages/public/public-page.html - 177 + 176 @@ -2486,7 +2498,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 53 + 72 @@ -2510,7 +2522,7 @@ Besparingen libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 397 + 424 @@ -2522,11 +2534,11 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 352 + 359 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 387 + 414 libs/ui/src/lib/i18n.ts @@ -2535,7 +2547,7 @@ annual interest rate - annual interest rate + jaarlijkse rente apps/client/src/app/pages/portfolio/fire/fire-page.html 185 @@ -2546,7 +2558,7 @@ Storting libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 377 + 404 @@ -2554,7 +2566,7 @@ Maandelijks apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 91 + 90 @@ -2578,11 +2590,11 @@ Angst apps/client/src/app/components/home-market/home-market.component.ts - 42 + 41 apps/client/src/app/components/markets/markets.component.ts - 47 + 46 libs/ui/src/lib/i18n.ts @@ -2594,11 +2606,11 @@ Hebzucht apps/client/src/app/components/home-market/home-market.component.ts - 43 + 42 apps/client/src/app/components/markets/markets.component.ts - 48 + 47 libs/ui/src/lib/i18n.ts @@ -2610,7 +2622,7 @@ Filter op... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 385 + 383 @@ -2646,7 +2658,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -2655,14 +2667,14 @@ Could not validate form - Could not validate form + Het formulier kon niet worden gevalideerd. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -2686,7 +2698,7 @@ Uitgesloten van analyse apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 267 + 274 @@ -2694,7 +2706,7 @@ Automatisch apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 70 + 69 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2730,7 +2742,7 @@ Totaalbedrag apps/client/src/app/components/investment-chart/investment-chart.component.ts - 143 + 146 @@ -2782,7 +2794,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2790,7 +2802,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 290 + 286 libs/ui/src/lib/i18n.ts @@ -2806,7 +2818,7 @@ Symbool apps/client/src/app/components/admin-jobs/admin-jobs.html - 68 + 77 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -2822,7 +2834,7 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 315 + 318 libs/ui/src/lib/i18n.ts @@ -2846,7 +2858,7 @@ Contant geld apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 212 + 219 libs/ui/src/lib/i18n.ts @@ -2891,10 +2903,10 @@ Authentication - Authentication + Authenticatie apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 35 + 54 @@ -2958,7 +2970,7 @@ Noodfonds apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 164 + 168 apps/client/src/app/pages/features/features-page.html @@ -2978,7 +2990,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 437 + 451 @@ -2990,7 +3002,7 @@ apps/client/src/app/pages/public/public-page.html - 196 + 195 libs/ui/src/lib/benchmark/benchmark.component.html @@ -2998,11 +3010,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 439 + 453 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 452 + 467 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3043,7 +3055,7 @@ If you retire today, you would be able to withdraw - If you retire today, you would be able to withdraw + Als u vandaag met pensioen gaat, kunt u apps/client/src/app/pages/portfolio/fire/fire-page.html 68 @@ -3106,12 +3118,12 @@ Symbool toewijzen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 Looking for a student discount? - Looking for a student discount? + Op zoek naar studentenkorting? apps/client/src/app/pages/pricing/pricing-page.html 342 @@ -3130,7 +3142,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 365 + 372 apps/client/src/app/pages/features/features-page.html @@ -3138,11 +3150,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 202 + 198 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 76 + 75 libs/ui/src/lib/i18n.ts @@ -3170,7 +3182,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3178,7 +3190,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 309 + 305 libs/ui/src/lib/i18n.ts @@ -3198,7 +3210,7 @@ Gegevens valideren... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 299 + 293 @@ -3214,7 +3226,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 71 + 67 @@ -3222,7 +3234,7 @@ Marktgegevens apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 398 + 403 libs/common/src/lib/routes/routes.ts @@ -3274,7 +3286,7 @@ Jaarlijks apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 91 @@ -3282,7 +3294,7 @@ Importeer dividenden apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 137 + 136 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3298,7 +3310,7 @@ Geldig tot apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -3337,6 +3349,14 @@ 22 + + No Activities + Geen activiteiten + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 145 + + Retirement Provision Pensioen @@ -3347,7 +3367,7 @@ Everything in Basic, plus - Everything in Basic, plus + Alles van Basic, plus apps/client/src/app/pages/pricing/pricing-page.html 199 @@ -3607,14 +3627,14 @@ Could not save asset profile - Could not save asset profile + Kon het assetprofiel niet opslaan apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -3634,7 +3654,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 88 + 92 @@ -3798,7 +3818,7 @@ Weet je zeker dat je alle activiteiten wilt verwijderen? libs/ui/src/lib/activities-table/activities-table.component.ts - 278 + 282 @@ -3809,9 +3829,17 @@ 306 + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 + + By - By + Tegen apps/client/src/app/pages/portfolio/fire/fire-page.html 139 @@ -3827,10 +3855,10 @@ Current year - Current year + Huidig jaar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 @@ -3846,11 +3874,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3863,10 +3891,10 @@ Asset profile has been saved - Asset profile has been saved + Het activaprofiel is opgeslagen. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -3874,7 +3902,7 @@ Wil je dit platform echt verwijderen? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 111 @@ -3882,7 +3910,7 @@ Platforms apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -3890,7 +3918,7 @@ Saldo bijwerken apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 112 + 108 @@ -4055,7 +4083,7 @@ View Details - View Details + Bekijk details apps/client/src/app/components/admin-users/admin-users.html 225 @@ -4070,7 +4098,7 @@ Verplichtingen apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 295 + 302 apps/client/src/app/pages/features/features-page.html @@ -4191,7 +4219,7 @@ per week - per week + per week apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 130 @@ -4215,7 +4243,7 @@ and we share aggregated key metrics of the platform’s performance - and we share aggregated key metrics of the platform’s performance + en we delen geaggregeerde belangrijke prestatiegegevens van het platform apps/client/src/app/pages/about/overview/about-overview-page.html 32 @@ -4226,7 +4254,7 @@ Scraper instellingen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -4259,7 +4287,7 @@ Website of Thomas Kaul - Website of Thomas Kaul + Website van Thomas Kaul apps/client/src/app/pages/about/overview/about-overview-page.html 44 @@ -4439,7 +4467,7 @@ Sign in with OpenID Connect - Sign in with OpenID Connect + Meld je aan met OpenID Connect apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 55 @@ -4470,7 +4498,7 @@ ETF’s zonder Landen apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 136 + 135 @@ -4478,7 +4506,7 @@ ETF’s zonder Sectoren apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 141 + 140 @@ -4486,7 +4514,7 @@ Assets apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 226 + 233 @@ -4531,7 +4559,7 @@ The source code is fully available as open source software (OSS) under the AGPL-3.0 license - The source code is fully available as open source software (OSS) under the AGPL-3.0 license + De broncode is volledig beschikbaar als open source software (OSS) onder de AGPL-3.0-licentie apps/client/src/app/pages/about/overview/about-overview-page.html 16 @@ -4603,7 +4631,7 @@ this is projected to increase to - this is projected to increase to + zal dit naar verwachting stijgen tot apps/client/src/app/pages/portfolio/fire/fire-page.html 147 @@ -4655,10 +4683,10 @@ Job ID - Job ID + Opdracht ID apps/client/src/app/components/admin-jobs/admin-jobs.html - 34 + 43 @@ -4714,11 +4742,11 @@ Valuta apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 131 + 130 apps/client/src/app/pages/public/public-page.html - 96 + 95 @@ -4739,7 +4767,7 @@ for - for + voor apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 128 @@ -4763,14 +4791,14 @@ Could not parse scraper configuration - Could not parse scraper configuration + De scraperconfiguratie kon niet worden geparseerd apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -4807,7 +4835,7 @@ Edit access - Edit access + Toegang bewerken apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 11 @@ -4879,7 +4907,7 @@ Get access to 80’000+ tickers from over 50 exchanges - Get access to 80’000+ tickers from over 50 exchanges + Krijg toegang tot meer dan 80.000+ tickers van meer dan 50 beurzen apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 84 @@ -5063,7 +5091,7 @@ less than - less than + minder dan apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 129 @@ -5353,7 +5381,7 @@ Ghostfolio Status - Ghostfolio Status + Ghostfolio Status apps/client/src/app/pages/about/overview/about-overview-page.html 62 @@ -5361,7 +5389,7 @@ with your university e-mail address - with your university e-mail address + met uw universitaire e-mailadres apps/client/src/app/pages/pricing/pricing-page.html 348 @@ -5381,7 +5409,7 @@ and a safe withdrawal rate (SWR) of - and a safe withdrawal rate (SWR) of + en een veilige opnameratio (SWR) van apps/client/src/app/pages/portfolio/fire/fire-page.html 108 @@ -5432,7 +5460,7 @@ Kosten apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 262 + 258 libs/ui/src/lib/activities-table/activities-table.component.html @@ -5464,7 +5492,7 @@ Weet u zetker dat u dit label wilt verwijderen? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 108 @@ -5532,7 +5560,7 @@ Lidmaatschap apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 48 + 67 libs/common/src/lib/routes/routes.ts @@ -5545,7 +5573,7 @@ Request it - Request it + Aanvragen apps/client/src/app/pages/pricing/pricing-page.html 344 @@ -5564,7 +5592,7 @@ Bezittingen Profiel apps/client/src/app/components/admin-jobs/admin-jobs.html - 52 + 61 @@ -5601,7 +5629,7 @@ , - , + , apps/client/src/app/pages/portfolio/fire/fire-page.html 145 @@ -5617,7 +5645,7 @@ per month - per month + per maand apps/client/src/app/pages/portfolio/fire/fire-page.html 94 @@ -5688,7 +5716,7 @@ Oeps! Ophalen van historische data is mislukt. libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts - 262 + 284 @@ -5696,7 +5724,7 @@ Wilt u dit systeembericht echt verwijderen? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 206 + 209 @@ -5720,7 +5748,7 @@ Contant Saldo apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 148 + 146 @@ -5740,7 +5768,7 @@ Wilt u dit rekeningsaldo echt verwijderen? libs/ui/src/lib/account-balances/account-balances.component.ts - 120 + 113 @@ -5756,7 +5784,7 @@ De huidige markt waarde is apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5764,7 +5792,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5809,7 +5837,7 @@ Argentina - Argentina + Argentinië libs/ui/src/lib/i18n.ts 78 @@ -5852,11 +5880,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 81 + 80 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 97 + 96 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5865,7 +5893,7 @@ here - here + hier apps/client/src/app/pages/pricing/pricing-page.html 347 @@ -5873,10 +5901,10 @@ Close Holding - Close Holding + Sluit Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 447 @@ -5916,7 +5944,7 @@ Week tot nu toe libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5924,11 +5952,11 @@ Week tot nu toe apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5936,7 +5964,7 @@ Maand tot nu toe libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5944,11 +5972,11 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5956,7 +5984,7 @@ Jaar tot nu toe libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -5992,7 +6020,7 @@ jaar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6004,7 +6032,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -6012,11 +6040,11 @@ jaren apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -6032,7 +6060,7 @@ Data Verzamelen apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6044,7 +6072,7 @@ Algemeen apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6052,7 +6080,7 @@ Cloud apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6064,7 +6092,7 @@ Zelf Hosten apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6105,7 +6133,7 @@ Actief apps/client/src/app/components/home-holdings/home-holdings.component.ts - 64 + 63 @@ -6113,7 +6141,7 @@ Gesloten apps/client/src/app/components/home-holdings/home-holdings.component.ts - 65 + 64 @@ -6145,7 +6173,7 @@ Opdracht Uitvoeren apps/client/src/app/components/admin-jobs/admin-jobs.html - 220 + 229 @@ -6153,7 +6181,7 @@ Prioriteit apps/client/src/app/components/admin-jobs/admin-jobs.html - 96 + 105 @@ -6209,7 +6237,7 @@ Wilt u uw Ghostfolio account echt sluiten? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 205 @@ -6254,10 +6282,10 @@ Include in - Include in + Opnemen in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6265,7 +6293,7 @@ Oeps! Er is een fout opgetreden met het instellen van de biometrische authenticatie. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 336 + 333 @@ -6281,7 +6309,7 @@ Benchmarks apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 126 + 125 @@ -6329,7 +6357,7 @@ Wilt u uw persoonlijke belegginngsstrategie verfijnen? apps/client/src/app/pages/public/public-page.html - 234 + 233 @@ -6538,7 +6566,7 @@ View Holding - View Holding + Bekijk Holding libs/ui/src/lib/activities-table/activities-table.component.html 450 @@ -6661,7 +6689,7 @@ Fout apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6682,7 +6710,7 @@ Oops! Could not update access. - Oops! Could not update access. + Oops! Kan de toegang niet updaten. apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts 178 @@ -6690,7 +6718,7 @@ , based on your total assets of - , based on your total assets of + opnemen, dit is gebaseerd op uw totale vermogen van apps/client/src/app/pages/portfolio/fire/fire-page.html 96 @@ -6713,7 +6741,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6745,7 +6773,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 345 + 341 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -6753,7 +6781,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 46 + 47 libs/ui/src/lib/i18n.ts @@ -6765,7 +6793,7 @@ Sluiten apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6791,13 +6819,17 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 68 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 127 + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 107 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 347 + 343 libs/ui/src/lib/i18n.ts @@ -6806,10 +6838,10 @@ Role - Role + Rol apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 14 + 33 @@ -6833,7 +6865,7 @@ Portfolio Momentopname apps/client/src/app/components/admin-jobs/admin-jobs.html - 56 + 65 @@ -6846,7 +6878,7 @@ If you plan to open an account at - If you plan to open an account at + Als u van plan bent een rekening te openen bij apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -6878,7 +6910,7 @@ send an e-mail to - send an e-mail to + stuur een e-mail naar apps/client/src/app/pages/about/overview/about-overview-page.html 87 @@ -6900,6 +6932,14 @@ 42 + + has been copied to the clipboard + has been copied to the clipboard + + libs/ui/src/lib/value/value.component.ts + 180 + + From the beginning Vanaf het begin @@ -6950,7 +6990,7 @@ , assuming a - , assuming a + , uitgaande van apps/client/src/app/pages/portfolio/fire/fire-page.html 174 @@ -7038,7 +7078,7 @@ Ghostfolio is a lightweight wealth management application for individuals to keep track of stocks, ETFs or cryptocurrencies and make solid, data-driven investment decisions. - Ghostfolio is a lightweight wealth management application for individuals to keep track of stocks, ETFs or cryptocurrencies and make solid, data-driven investment decisions. + Ghostfolio is een gebruiksvriendelijke applicatie voor vermogensbeheer waarmee particulieren hun aandelen, ETF's of cryptovaluta kunnen volgen en weloverwogen, datagestuurde beleggingsbeslissingen kunnen nemen. apps/client/src/app/pages/about/overview/about-overview-page.html 10 @@ -7065,7 +7105,7 @@ API-sleutel instellen apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7101,7 +7141,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 45 + 44 libs/common/src/lib/routes/routes.ts @@ -7117,7 +7157,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 34 + 33 libs/common/src/lib/routes/routes.ts @@ -7171,7 +7211,7 @@ van apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7179,7 +7219,7 @@ dagelijkse verzoeken apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7187,7 +7227,7 @@ Verwijder API-sleutel apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7195,7 +7235,7 @@ Wilt u de API-sleutel echt verwijderen? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7215,7 +7255,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 98 + 117 @@ -7287,7 +7327,7 @@ Opslaan apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7323,11 +7363,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 356 + 352 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 48 + 49 @@ -7339,7 +7379,7 @@ apps/client/src/app/components/user-account-access/user-account-access.component.ts - 260 + 257 @@ -7363,12 +7403,12 @@ Voer uw Ghostfolio API-sleutel in. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 Change with currency effect - Change with currency effect + Verandering met valuta-effect apps/client/src/app/pages/portfolio/analysis/analysis-page.html 116 @@ -7379,7 +7419,7 @@ AI-prompt is naar het klembord gekopieerd apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 201 + 199 @@ -7395,7 +7435,7 @@ Lui apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7403,7 +7443,7 @@ Direct apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7411,7 +7451,7 @@ Standaard Marktprijs apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7419,7 +7459,7 @@ Modus apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7427,7 +7467,7 @@ Kiezer apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7435,7 +7475,7 @@ HTTP Verzoek Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7443,7 +7483,7 @@ eind van de dag apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7451,7 +7491,7 @@ real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7459,7 +7499,7 @@ Open Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 202 + 200 @@ -7479,7 +7519,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 @@ -7491,7 +7531,7 @@ apps/client/src/app/components/home-overview/home-overview.component.ts - 55 + 54 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -7499,16 +7539,16 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 381 + 390 The project has been initiated by - The project has been initiated by + Het project is geïnitieerd door apps/client/src/app/pages/about/overview/about-overview-page.html 40 @@ -7532,7 +7572,7 @@ Total amount - Total amount + Totaal bedrag apps/client/src/app/pages/portfolio/analysis/analysis-page.html 95 @@ -7607,11 +7647,11 @@ Beveiligingstoken apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 235 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 170 + 167 @@ -7619,15 +7659,15 @@ Wilt u echt een nieuw beveiligingstoken voor deze gebruiker aanmaken? apps/client/src/app/components/admin-users/admin-users.component.ts - 241 + 240 Find account, holding or page... - Find account, holding or page... + Vindt een account, holding of pagina... libs/ui/src/lib/assistant/assistant.component.ts - 151 + 115 @@ -7692,7 +7732,7 @@ () is al in gebruik. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7700,7 +7740,7 @@ Er is een fout opgetreden tijdens het updaten naar (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7764,7 +7804,7 @@ iemand apps/client/src/app/pages/public/public-page.component.ts - 59 + 61 @@ -7796,7 +7836,7 @@ Wilt u dit item echt verwijderen? libs/ui/src/lib/benchmark/benchmark.component.ts - 144 + 139 @@ -7837,7 +7877,7 @@ Demo-gebruikersaccount is gesynchroniseerd. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 274 + 277 @@ -7872,7 +7912,7 @@ 150 - + Fee Ratio Vergoedingsverhouding @@ -7880,17 +7920,17 @@ 152 - - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - De kosten overschrijden ${thresholdMax}% van uw initiële investering (${feeRatio}%) + + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + De kosten overschrijden ${thresholdMax}% van uw totale investeringsvolume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 - - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - De kosten bedragen niet meer dan ${thresholdMax}% van uw initiële investering (${feeRatio}%) + + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + De kosten bedragen maximaal ${thresholdMax}% van uw totale investeringsvolume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -8048,10 +8088,10 @@ Current month - Current month + Huidige maand apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 @@ -8059,11 +8099,11 @@ nieuw apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts - 56 + 53 @@ -8220,7 +8260,7 @@ Wilt u echt een nieuwe securitytoken genereren? apps/client/src/app/components/user-account-access/user-account-access.component.ts - 175 + 172 @@ -8233,7 +8273,7 @@ If you encounter a bug, would like to suggest an improvement or a new feature, please join the Ghostfolio Slack community, post to @ghostfolio_ - If you encounter a bug, would like to suggest an improvement or a new feature, please join the Ghostfolio Slack community, post to @ghostfolio_ + Als je een bug tegenkomt, een verbetering wilt voorstellen of een nieuwe functie wilt toevoegen, word dan lid van de Ghostfolio Slack-community. Stuur een bericht naar @ghostfolio_ apps/client/src/app/pages/about/overview/about-overview-page.html 69 @@ -8244,7 +8284,7 @@ Aandelen apps/client/src/app/components/markets/markets.component.ts - 52 + 51 apps/client/src/app/pages/features/features-page.html @@ -8256,7 +8296,7 @@ Cryptovaluta apps/client/src/app/components/markets/markets.component.ts - 53 + 52 apps/client/src/app/pages/features/features-page.html @@ -8276,7 +8316,7 @@ Beheer activaprofiel apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 466 + 471 @@ -8357,7 +8397,7 @@ Liquidity - Liquidity + Liquiditeit apps/client/src/app/pages/i18n/i18n-page.html 70 @@ -8365,7 +8405,7 @@ Buying Power - Buying Power + Koopkracht apps/client/src/app/pages/i18n/i18n-page.html 71 @@ -8373,7 +8413,7 @@ Your buying power is below ${thresholdMin} ${baseCurrency} - Your buying power is below ${thresholdMin} ${baseCurrency} + Uw koopkracht ligt onder ${thresholdMin} ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 73 @@ -8381,7 +8421,7 @@ Your buying power is 0 ${baseCurrency} - Your buying power is 0 ${baseCurrency} + Uw koopkracht is 0 ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 77 @@ -8389,7 +8429,7 @@ Your buying power exceeds ${thresholdMin} ${baseCurrency} - Your buying power exceeds ${thresholdMin} ${baseCurrency} + Uw koopkracht overschrijdt ${thresholdMin} ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 80 @@ -8693,10 +8733,10 @@ Registration Date - Registration Date + Registratiedatum apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 26 + 45 diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index a94a76d2f..a15609e6a 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -240,7 +240,7 @@ please - please + proszę apps/client/src/app/pages/pricing/pricing-page.html 333 @@ -251,7 +251,7 @@ Typ apps/client/src/app/components/admin-jobs/admin-jobs.html - 48 + 57 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -284,7 +284,7 @@ with - with + z apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 87 @@ -351,7 +351,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -363,7 +363,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -379,7 +379,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 139 + 135 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -423,7 +423,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -435,7 +435,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 145 + 141 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -455,15 +455,15 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 205 + 201 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 208 + 204 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 211 + 207 libs/ui/src/lib/account-balances/account-balances.component.html @@ -511,11 +511,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -543,11 +543,15 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 libs/ui/src/lib/account-balances/account-balances.component.html @@ -571,7 +575,7 @@ Czy na pewno chcesz usunąć to konto? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 150 + 148 @@ -579,7 +583,7 @@ Profil Aktywów apps/client/src/app/components/admin-jobs/admin-jobs.html - 52 + 61 @@ -587,11 +591,11 @@ Historyczne Dane Rynkowe apps/client/src/app/components/admin-jobs/admin-jobs.html - 54 + 63 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -599,7 +603,7 @@ Źródło Danych apps/client/src/app/components/admin-jobs/admin-jobs.html - 82 + 91 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -611,7 +615,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 155 + 151 libs/ui/src/lib/i18n.ts @@ -623,7 +627,7 @@ Próby apps/client/src/app/components/admin-jobs/admin-jobs.html - 120 + 129 @@ -631,7 +635,7 @@ Utworzono apps/client/src/app/components/admin-jobs/admin-jobs.html - 134 + 143 @@ -639,7 +643,7 @@ Zakończono apps/client/src/app/components/admin-jobs/admin-jobs.html - 143 + 152 @@ -647,16 +651,16 @@ Status apps/client/src/app/components/admin-jobs/admin-jobs.html - 152 + 161 apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 and is driven by the efforts of its contributors - and is driven by the efforts of its contributors + i jest rozwijany dzięki pracy jego współtwórców apps/client/src/app/pages/about/overview/about-overview-page.html 49 @@ -667,7 +671,7 @@ Usuń Zadania apps/client/src/app/components/admin-jobs/admin-jobs.html - 193 + 202 @@ -675,7 +679,7 @@ Zobacz Dane apps/client/src/app/components/admin-jobs/admin-jobs.html - 208 + 217 @@ -683,7 +687,7 @@ Wyświetl Stos Wywołań apps/client/src/app/components/admin-jobs/admin-jobs.html - 216 + 225 @@ -691,7 +695,7 @@ Usuń Zadanie apps/client/src/app/components/admin-jobs/admin-jobs.html - 224 + 233 @@ -707,7 +711,7 @@ Data apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 161 + 157 libs/ui/src/lib/account-balances/account-balances.component.html @@ -743,11 +747,11 @@ Waluty apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 131 + 130 apps/client/src/app/pages/public/public-page.html - 96 + 95 @@ -755,7 +759,7 @@ ETF-y bez Krajów apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 136 + 135 @@ -763,7 +767,7 @@ ETF-y bez Sektorów apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 141 + 140 @@ -779,7 +783,7 @@ Filtruj według... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 385 + 383 @@ -819,7 +823,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 44 + 40 @@ -855,7 +859,7 @@ Ups! Nie udało się sparsować danych historycznych. libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts - 262 + 284 @@ -879,7 +883,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 71 + 67 @@ -911,7 +915,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 53 + 72 @@ -923,7 +927,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -931,7 +935,7 @@ apps/client/src/app/pages/public/public-page.html - 114 + 113 @@ -943,7 +947,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -955,7 +959,7 @@ Mapowanie Symboli apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -971,7 +975,7 @@ Konfiguracja Scrapera apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -979,7 +983,7 @@ Notatka apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -987,7 +991,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 275 + 271 @@ -1031,7 +1035,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 124 + 120 @@ -1039,7 +1043,7 @@ Czy naprawdę chcesz usunąć ten kupon? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 193 + 196 @@ -1047,7 +1051,7 @@ Czy naprawdę chcesz usunąć tę wiadomość systemową? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 206 + 209 @@ -1055,7 +1059,7 @@ Czy naprawdę chcesz wyczyścić pamięć podręczną? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 230 + 233 @@ -1063,7 +1067,7 @@ Proszę ustawić swoją wiadomość systemową: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 250 + 253 @@ -1187,11 +1191,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1204,10 +1208,10 @@ Asset profile has been saved - Asset profile has been saved + Profil zasobu został zapisany apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -1215,12 +1219,20 @@ Czy naprawdę chcesz usunąć tę platformę? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 111 + + + + Explore + Eksploruj + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 By - By + Przez apps/client/src/app/pages/portfolio/fire/fire-page.html 139 @@ -1236,10 +1248,10 @@ Current year - Current year + Obecny rok apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 @@ -1255,7 +1267,7 @@ Platformy apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -1263,7 +1275,7 @@ Tagi apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1287,7 +1299,7 @@ Czy naprawdę chcesz usunąć ten tag? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 108 @@ -1332,7 +1344,7 @@ No auto-renewal on membership. - No auto-renewal on membership. + Brak automatycznego odnawiania członkostwa. apps/client/src/app/components/user-account-membership/user-account-membership.html 74 @@ -1347,7 +1359,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 89 + 108 @@ -1379,11 +1391,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -1431,7 +1443,7 @@ Poziom Odniesienia (Benchmark) apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1467,7 +1479,7 @@ apps/client/src/app/components/header/header.component.ts - 297 + 298 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -1487,15 +1499,15 @@ Ups! Nieprawidłowy token bezpieczeństwa. apps/client/src/app/components/header/header.component.ts - 312 + 313 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 154 + 152 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 193 + 191 @@ -1503,7 +1515,7 @@ Zarządzaj Aktywnościami apps/client/src/app/components/home-holdings/home-holdings.html - 67 + 64 @@ -1511,11 +1523,11 @@ Zagrożenie apps/client/src/app/components/home-market/home-market.component.ts - 42 + 41 apps/client/src/app/components/markets/markets.component.ts - 47 + 46 libs/ui/src/lib/i18n.ts @@ -1527,11 +1539,11 @@ Zachłanność apps/client/src/app/components/home-market/home-market.component.ts - 43 + 42 apps/client/src/app/components/markets/markets.component.ts - 48 + 47 libs/ui/src/lib/i18n.ts @@ -1568,7 +1580,7 @@ The source code is fully available as open source software (OSS) under the AGPL-3.0 license - The source code is fully available as open source software (OSS) under the AGPL-3.0 license + Kod źródłowy jest w pełni dostępny jako oprogramowanie open source (OSS) na licencji AGPL-3.0 license apps/client/src/app/pages/about/overview/about-overview-page.html 16 @@ -1632,10 +1644,10 @@ Current week - Current week + Obecny tydzień apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 @@ -1655,7 +1667,7 @@ Całkowita Kwota apps/client/src/app/components/investment-chart/investment-chart.component.ts - 143 + 146 @@ -1767,7 +1779,7 @@ Bezwzględne Osiągi Brutto apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 73 + 77 @@ -1779,7 +1791,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 88 + 92 @@ -1787,7 +1799,7 @@ Bezwzględne Osiągi Netto apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 107 + 111 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -1799,7 +1811,7 @@ Osiągi Netto apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 123 + 127 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -1811,7 +1823,7 @@ Suma Aktywów apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 149 + 153 @@ -1819,7 +1831,7 @@ Aktywa apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 226 + 233 @@ -1827,7 +1839,7 @@ Siła Nabywcza apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 241 + 248 @@ -1835,7 +1847,7 @@ Wykluczone z Analizy apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 267 + 274 @@ -1843,7 +1855,7 @@ Pasywa (Zobowiązania Finansowe) apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 295 + 302 apps/client/src/app/pages/features/features-page.html @@ -1855,7 +1867,7 @@ Wartość Netto apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 317 + 324 @@ -1863,7 +1875,7 @@ Osiągi w Ujęciu Rocznym apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 331 + 338 @@ -1899,7 +1911,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 189 + 185 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1915,7 +1927,7 @@ Zgłoś Błąd Danych apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 451 + 456 @@ -2095,7 +2107,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 363 + 362 @@ -2103,11 +2115,11 @@ Liczony od początku roku (year-to-date) apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -2115,11 +2127,11 @@ 1 rok apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -2127,16 +2139,16 @@ 5 lat apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 Performance with currency effect - Performance with currency effect + Wynik z efektem walutowym apps/client/src/app/pages/portfolio/analysis/analysis-page.html 135 @@ -2147,11 +2159,11 @@ Maksimum apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 217 libs/ui/src/lib/assistant/assistant.component.ts - 415 + 414 @@ -2251,7 +2263,7 @@ Auto apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 70 + 69 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2263,7 +2275,7 @@ Czy na pewno chcesz usunąć tą metode logowania? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 279 @@ -2303,7 +2315,7 @@ Ustawienia Regionalne apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2364,7 +2376,7 @@ this is projected to increase to - this is projected to increase to + prognozuje się wzrost tej kwoty do apps/client/src/app/pages/portfolio/fire/fire-page.html 147 @@ -2415,7 +2427,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 11 + 30 @@ -2447,7 +2459,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 195 + 194 @@ -2459,7 +2471,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 193 + 192 @@ -2475,7 +2487,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 196 + 195 @@ -2576,7 +2588,7 @@ for - for + dla apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 128 @@ -2603,11 +2615,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 375 + 381 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 66 + 85 apps/client/src/app/pages/accounts/accounts-page.html @@ -2699,7 +2711,7 @@ Dane Rynkowe apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 398 + 403 libs/common/src/lib/routes/routes.ts @@ -2733,6 +2745,10 @@ Overview Przegląd + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 7 + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 114 @@ -2751,11 +2767,11 @@ apps/client/src/app/pages/admin/admin-page.component.ts - 48 + 45 apps/client/src/app/pages/resources/resources-page.component.ts - 30 + 29 libs/common/src/lib/routes/routes.ts @@ -2876,14 +2892,14 @@ Could not parse scraper configuration - Could not parse scraper configuration + Nie udało się przetworzyć konfiguracji scrapera apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -2899,7 +2915,7 @@ Ponieważ jesteś już zalogowany, nie możesz uzyskać dostępu do konta demo. apps/client/src/app/pages/demo/demo-page.component.ts - 35 + 32 @@ -3032,7 +3048,7 @@ per week - per week + na tydzień apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 130 @@ -3071,7 +3087,7 @@ apps/client/src/app/pages/public/public-page.html - 242 + 241 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -3123,7 +3139,7 @@ Rynki apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -3147,7 +3163,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 40 + 39 libs/common/src/lib/routes/routes.ts @@ -3208,7 +3224,7 @@ Edit access - Edit access + Edytuj dostęp apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 11 @@ -3312,7 +3328,7 @@ Get access to 80’000+ tickers from over 50 exchanges - Get access to 80’000+ tickers from over 50 exchanges + Uzyskaj dostęp do 80 000+ tickerów z ponad 50 giełd apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 84 @@ -3448,7 +3464,7 @@ less than - less than + mniej niż apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 129 @@ -3512,7 +3528,7 @@ Ghostfolio Status - Ghostfolio Status + Ghostfolio Status apps/client/src/app/pages/about/overview/about-overview-page.html 62 @@ -3520,7 +3536,7 @@ with your university e-mail address - with your university e-mail address + używając swojego adresu e-mail uczelni apps/client/src/app/pages/pricing/pricing-page.html 348 @@ -3552,7 +3568,7 @@ and a safe withdrawal rate (SWR) of - and a safe withdrawal rate (SWR) of + oraz bezpiecznej stopy wypłaty (SWR) na poziomie apps/client/src/app/pages/portfolio/fire/fire-page.html 108 @@ -3579,7 +3595,7 @@ Job ID apps/client/src/app/components/admin-jobs/admin-jobs.html - 34 + 43 @@ -3615,7 +3631,7 @@ apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 115 + 113 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -3635,11 +3651,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 342 + 348 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 75 + 94 apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -3659,7 +3675,7 @@ Czy na pewno chcesz usunąć te aktywności? libs/ui/src/lib/activities-table/activities-table.component.ts - 278 + 282 @@ -3727,7 +3743,7 @@ Zaktualizuj Saldo Gotówkowe apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 112 + 108 @@ -3735,7 +3751,7 @@ Cena Jednostkowa apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 214 + 210 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3763,7 +3779,7 @@ Impotruj Dywidendy apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 137 + 136 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3779,7 +3795,7 @@ Importowanie danych... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 175 + 174 @@ -3787,12 +3803,12 @@ Importowanie zakończone apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 185 + 184 or start a discussion at - or start a discussion at + lub rozpocznij dyskusję n apps/client/src/app/pages/about/overview/about-overview-page.html 94 @@ -3803,7 +3819,7 @@ Weryfikacja danych... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 299 + 293 @@ -3975,7 +3991,7 @@ apps/client/src/app/pages/public/public-page.html - 151 + 150 @@ -3999,15 +4015,15 @@ apps/client/src/app/pages/public/public-page.html - 168 + 167 Latest activities - Latest activities + Ostatnie transakcje apps/client/src/app/pages/public/public-page.html - 211 + 210 @@ -4019,7 +4035,7 @@ apps/client/src/app/pages/public/public-page.html - 177 + 176 @@ -4031,7 +4047,7 @@ apps/client/src/app/pages/public/public-page.html - 186 + 185 @@ -4072,7 +4088,7 @@ Looking for a student discount? - Looking for a student discount? + Szukasz zniżki studenckiej? apps/client/src/app/pages/pricing/pricing-page.html 342 @@ -4091,7 +4107,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 365 + 372 apps/client/src/app/pages/features/features-page.html @@ -4099,11 +4115,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 202 + 198 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 76 + 75 libs/ui/src/lib/i18n.ts @@ -4112,7 +4128,7 @@ annual interest rate - annual interest rate + rocznej stopy zwrotu apps/client/src/app/pages/portfolio/fire/fire-page.html 185 @@ -4123,7 +4139,7 @@ Depozyt libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 377 + 404 @@ -4131,7 +4147,7 @@ Miesięcznie apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 91 + 90 @@ -4139,7 +4155,7 @@ Rocznie apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 91 @@ -4419,11 +4435,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -4447,12 +4463,12 @@ Kontynenty apps/client/src/app/pages/public/public-page.html - 132 + 131 Sustainable retirement income - Sustainable retirement income + Zrównoważony dochód na emeryturze apps/client/src/app/pages/portfolio/fire/fire-page.html 41 @@ -4463,7 +4479,7 @@ Ghostfolio umożliwia śledzenie wartości swojego majątku. apps/client/src/app/pages/public/public-page.html - 238 + 237 @@ -4493,6 +4509,10 @@ apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 88 + + libs/ui/src/lib/value/value.component.html + 18 + Personal Finance Tools @@ -4585,7 +4605,7 @@ per month - per month + miesięcznie apps/client/src/app/pages/portfolio/fire/fire-page.html 94 @@ -4605,7 +4625,7 @@ Website of Thomas Kaul - Website of Thomas Kaul + Strona internetowa Thomas'a Kaul'a apps/client/src/app/pages/about/overview/about-overview-page.html 44 @@ -4824,7 +4844,7 @@ Członkostwo apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 48 + 67 libs/common/src/lib/routes/routes.ts @@ -4837,7 +4857,7 @@ Request it - Request it + Porpoś o apps/client/src/app/pages/pricing/pricing-page.html 344 @@ -4940,7 +4960,7 @@ Czy na pewno chcesz usunąć tę działalność? libs/ui/src/lib/activities-table/activities-table.component.ts - 288 + 292 @@ -4948,7 +4968,7 @@ Profile aktywów apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -4957,7 +4977,7 @@ , - , + , apps/client/src/app/pages/portfolio/fire/fire-page.html 145 @@ -4981,7 +5001,7 @@ contact us - contact us + skontaktuj się z nami apps/client/src/app/pages/pricing/pricing-page.html 336 @@ -5037,18 +5057,18 @@ Interest - Udział + Odsetki apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html 69 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 352 + 359 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 387 + 414 libs/ui/src/lib/i18n.ts @@ -5060,7 +5080,7 @@ Oszczędności libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 397 + 424 @@ -5088,7 +5108,7 @@ Pokaż wszystko libs/ui/src/lib/holdings-table/holdings-table.component.html - 216 + 212 @@ -5132,7 +5152,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5140,7 +5160,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 290 + 286 libs/ui/src/lib/i18n.ts @@ -5164,7 +5184,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5172,7 +5192,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 309 + 305 libs/ui/src/lib/i18n.ts @@ -5216,7 +5236,7 @@ Fundusz Rezerwowy apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 164 + 168 apps/client/src/app/pages/features/features-page.html @@ -5292,7 +5312,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 437 + 451 @@ -5303,6 +5323,14 @@ 27 + + No Activities + Brak transakcji + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 145 + + Retirement Provision Świadczenia Emerytalne @@ -5332,7 +5360,7 @@ Symbol apps/client/src/app/components/admin-jobs/admin-jobs.html - 68 + 77 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -5348,7 +5376,7 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 315 + 318 libs/ui/src/lib/i18n.ts @@ -5377,7 +5405,7 @@ View Details - View Details + Zobacz szczegóły apps/client/src/app/components/admin-users/admin-users.html 225 @@ -5397,7 +5425,7 @@ Sign in with OpenID Connect - Sign in with OpenID Connect + Zaloguj się za pomocą OpenID Connect apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 55 @@ -5405,7 +5433,7 @@ Buy - Zakup + Kupno apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html 31 @@ -5420,7 +5448,7 @@ Opłata apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 262 + 258 libs/ui/src/lib/activities-table/activities-table.component.html @@ -5449,7 +5477,7 @@ Sell - Sprzedaj + Sprzedaż apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html 44 @@ -5464,7 +5492,7 @@ Gotówka apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 212 + 219 libs/ui/src/lib/i18n.ts @@ -5509,10 +5537,10 @@ Authentication - Authentication + Uwierzytelnianie apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 35 + 54 @@ -5605,7 +5633,7 @@ If you retire today, you would be able to withdraw - If you retire today, you would be able to withdraw + Gdybyś przeszedł na emeryturę dziś, mógłbyś wypłacać apps/client/src/app/pages/portfolio/fire/fire-page.html 68 @@ -5656,7 +5684,7 @@ Ważność do apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -5680,7 +5708,7 @@ apps/client/src/app/pages/public/public-page.html - 196 + 195 libs/ui/src/lib/benchmark/benchmark.component.html @@ -5688,11 +5716,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 439 + 453 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 452 + 467 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -5720,7 +5748,7 @@ Salda Gotówkowe apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 148 + 146 @@ -5740,7 +5768,7 @@ Czy na pewno chcesz usunąć saldo tego konta? libs/ui/src/lib/account-balances/account-balances.component.ts - 120 + 113 @@ -5756,7 +5784,7 @@ Obecna cena rynkowa wynosi apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5764,7 +5792,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5809,7 +5837,7 @@ Argentina - Argentina + Argentyna libs/ui/src/lib/i18n.ts 78 @@ -5852,11 +5880,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 81 + 80 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 97 + 96 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5865,7 +5893,7 @@ here - here + tutaj apps/client/src/app/pages/pricing/pricing-page.html 347 @@ -5873,10 +5901,10 @@ Close Holding - Close Holding + Zamknij pozycję apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 447 @@ -5916,7 +5944,7 @@ Dotychczasowy tydzień libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5924,11 +5952,11 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5936,7 +5964,7 @@ Od początku miesiąca libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5944,11 +5972,11 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5956,7 +5984,7 @@ Od początku roku libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -5992,7 +6020,7 @@ rok apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6004,7 +6032,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -6012,11 +6040,11 @@ lata apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -6032,7 +6060,7 @@ Gromadzenie Danych apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6044,7 +6072,7 @@ Informacje Ogólne apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6052,7 +6080,7 @@ Rozwiązanie w Chmurze apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6064,7 +6092,7 @@ Własny Hosting apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6105,7 +6133,7 @@ Antywne apps/client/src/app/components/home-holdings/home-holdings.component.ts - 64 + 63 @@ -6113,7 +6141,7 @@ Zamknięte apps/client/src/app/components/home-holdings/home-holdings.component.ts - 65 + 64 @@ -6145,7 +6173,7 @@ Wykonaj Zadanie apps/client/src/app/components/admin-jobs/admin-jobs.html - 220 + 229 @@ -6153,7 +6181,7 @@ Priorytet apps/client/src/app/components/admin-jobs/admin-jobs.html - 96 + 105 @@ -6209,7 +6237,7 @@ Czy na pewno chcesz zamknąć swoje konto Ghostfolio? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 205 @@ -6254,10 +6282,10 @@ Include in - Include in + Uwzględnij w apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6265,7 +6293,7 @@ Ups! Wystąpił błąd podczas konfigurowania uwierzytelniania biometrycznego. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 336 + 333 @@ -6281,7 +6309,7 @@ Punkty Odniesienia apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 126 + 125 @@ -6329,7 +6357,7 @@ Chcesz udoskonalić swoją osobistą strategię inwestycyjną? apps/client/src/app/pages/public/public-page.html - 234 + 233 @@ -6538,7 +6566,7 @@ View Holding - View Holding + Podgląd inwestycji libs/ui/src/lib/activities-table/activities-table.component.html 450 @@ -6661,7 +6689,7 @@ Błąd apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6682,7 +6710,7 @@ Oops! Could not update access. - Oops! Could not update access. + Ups! Nie udało się zaktualizować dostępu. apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts 178 @@ -6690,7 +6718,7 @@ , based on your total assets of - , based on your total assets of + , na podstawie całkowitej wartości aktywów wynoszącej apps/client/src/app/pages/portfolio/fire/fire-page.html 96 @@ -6713,7 +6741,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6745,7 +6773,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 345 + 341 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -6753,7 +6781,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 46 + 47 libs/ui/src/lib/i18n.ts @@ -6765,7 +6793,7 @@ Zamknij apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6791,13 +6819,17 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 68 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 127 + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 107 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 347 + 343 libs/ui/src/lib/i18n.ts @@ -6806,10 +6838,10 @@ Role - Role + Rola apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 14 + 33 @@ -6833,7 +6865,7 @@ Przegląd portfela apps/client/src/app/components/admin-jobs/admin-jobs.html - 56 + 65 @@ -6846,7 +6878,7 @@ If you plan to open an account at - If you plan to open an account at + Jeśli planujesz otworzyć konto w apps/client/src/app/pages/pricing/pricing-page.html 312 @@ -6878,7 +6910,7 @@ send an e-mail to - send an e-mail to + wyślij e-mail do apps/client/src/app/pages/about/overview/about-overview-page.html 87 @@ -6900,6 +6932,14 @@ 42 + + has been copied to the clipboard + has been copied to the clipboard + + libs/ui/src/lib/value/value.component.ts + 180 + + From the beginning Od samego początku @@ -6950,7 +6990,7 @@ , assuming a - , assuming a + , przyjmując apps/client/src/app/pages/portfolio/fire/fire-page.html 174 @@ -6958,7 +6998,7 @@ to use our referral link and get a Ghostfolio Premium membership for one year - to use our referral link and get a Ghostfolio Premium membership for one year + aby skorzystać z naszego linku polecającego i otrzymać roczną subskrypcję Ghostfolio Premium apps/client/src/app/pages/pricing/pricing-page.html 340 @@ -6966,7 +7006,7 @@ can be self-hosted - może być hostowany samodzielnie + może być hostowana samodzielnie apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 178 @@ -6990,7 +7030,7 @@ can be used anonymously - może być używany anonimowo + może być używana anonimowo apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 217 @@ -7002,7 +7042,7 @@ cannot be used anonymously - nie może być używany anonimowo + nie może być używana anonimowo apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 224 @@ -7038,7 +7078,7 @@ Ghostfolio is a lightweight wealth management application for individuals to keep track of stocks, ETFs or cryptocurrencies and make solid, data-driven investment decisions. - Ghostfolio is a lightweight wealth management application for individuals to keep track of stocks, ETFs or cryptocurrencies and make solid, data-driven investment decisions. + Ghostfolio to aplikacja do zarządzania majątkiem, przeznaczona dla osób prywatnych do śledzenia akcji, ETF‑ów i kryptowalut oraz podejmowania solidnych, opartych na danych decyzji inwestycyjnych. apps/client/src/app/pages/about/overview/about-overview-page.html 10 @@ -7065,7 +7105,7 @@ Skonfiguruj klucz API apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7101,7 +7141,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 45 + 44 libs/common/src/lib/routes/routes.ts @@ -7117,7 +7157,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 34 + 33 libs/common/src/lib/routes/routes.ts @@ -7171,7 +7211,7 @@ z apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7179,7 +7219,7 @@ codzienne żądania apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7187,7 +7227,7 @@ Usuń klucz API apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7195,7 +7235,7 @@ Czy na pewno chcesz usunąć klucz API?? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7215,7 +7255,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 98 + 117 @@ -7287,7 +7327,7 @@ Zapisz apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7323,11 +7363,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 356 + 352 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 48 + 49 @@ -7339,7 +7379,7 @@ apps/client/src/app/components/user-account-access/user-account-access.component.ts - 260 + 257 @@ -7352,7 +7392,7 @@ Check the system status at - Check the system status at + Sprawdź status systemu n apps/client/src/app/pages/about/overview/about-overview-page.html 57 @@ -7363,12 +7403,12 @@ Wprowadź swój klucz API Ghostfolio. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 Change with currency effect - Change with currency effect + Zmiana z efektem walutowym apps/client/src/app/pages/portfolio/analysis/analysis-page.html 116 @@ -7379,7 +7419,7 @@ Prompt AI został skopiowany do schowka apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 201 + 199 @@ -7395,7 +7435,7 @@ Leniwy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7403,7 +7443,7 @@ Natychmiastowy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7411,7 +7451,7 @@ Domyślna cena rynkowa apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7419,7 +7459,7 @@ Tryb apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7427,7 +7467,7 @@ Selektor apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7435,7 +7475,7 @@ Nagłówki żądań HTTP apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7443,7 +7483,7 @@ koniec dnia apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7451,7 +7491,7 @@ w czasie rzeczywistym apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7459,7 +7499,7 @@ Otwórz Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 202 + 200 @@ -7479,7 +7519,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 @@ -7491,7 +7531,7 @@ apps/client/src/app/components/home-overview/home-overview.component.ts - 55 + 54 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -7499,16 +7539,16 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 381 + 390 The project has been initiated by - The project has been initiated by + Projekt został zainicjowany przez apps/client/src/app/pages/about/overview/about-overview-page.html 40 @@ -7532,7 +7572,7 @@ Total amount - Total amount + Wartość portfela apps/client/src/app/pages/portfolio/analysis/analysis-page.html 95 @@ -7607,11 +7647,11 @@ Token bezpieczeństwa apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 235 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 170 + 167 @@ -7619,15 +7659,15 @@ Czy napewno chcesz wygenerować nowy token bezpieczeństwa dla tego użytkownika? apps/client/src/app/components/admin-users/admin-users.component.ts - 241 + 240 Find account, holding or page... - Find account, holding or page... + Znajdź konto, pozycję lub stronę... libs/ui/src/lib/assistant/assistant.component.ts - 151 + 115 @@ -7692,7 +7732,7 @@ () jest już w użyciu. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7700,7 +7740,7 @@ Wystąpił błąd podczas aktualizacji do (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7764,7 +7804,7 @@ ktoś apps/client/src/app/pages/public/public-page.component.ts - 59 + 61 @@ -7796,7 +7836,7 @@ Czy na pewno chcesz usunąć ten element? libs/ui/src/lib/benchmark/benchmark.component.ts - 144 + 139 @@ -7837,7 +7877,7 @@ Konto użytkownika demonstracyjnego zostało zsynchronizowane. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 274 + 277 @@ -7872,25 +7912,25 @@ 150 - + Fee Ratio - Stosunek opłat + Wskaźnik opłat apps/client/src/app/pages/i18n/i18n-page.html 152 - - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - Opłaty przekraczają ${thresholdMax}% początkowej inwestycji (${feeRatio}%) + + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + Opłaty przekraczają ${thresholdMax}% twojej całkowitej wartości inwestycji (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 - - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - Opłaty nie przekraczają ${thresholdMax}% początkowej inwestycji (${feeRatio}%) + + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + Opłaty nie przekraczają ${thresholdMax}% twojej całkowitej wartości inwestycji (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -8048,10 +8088,10 @@ Current month - Current month + Bieżący miesiąc apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 @@ -8059,11 +8099,11 @@ nowy apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts - 56 + 53 @@ -8220,7 +8260,7 @@ Czy na pewno chcesz wygenerować nowy token bezpieczeństwa? apps/client/src/app/components/user-account-access/user-account-access.component.ts - 175 + 172 @@ -8244,7 +8284,7 @@ Akcje apps/client/src/app/components/markets/markets.component.ts - 52 + 51 apps/client/src/app/pages/features/features-page.html @@ -8256,7 +8296,7 @@ Kryptowaluty apps/client/src/app/components/markets/markets.component.ts - 53 + 52 apps/client/src/app/pages/features/features-page.html @@ -8276,7 +8316,7 @@ Zarządzaj profilem aktywów apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 466 + 471 @@ -8309,7 +8349,7 @@ Account Cluster Risks - Account Cluster Risks + Ryzyka skupienia w obrębie rachunków apps/client/src/app/pages/i18n/i18n-page.html 14 @@ -8317,7 +8357,7 @@ Asset Class Cluster Risks - Asset Class Cluster Risks + Ryzyka skupienia w obrębie klas aktywów apps/client/src/app/pages/i18n/i18n-page.html 39 @@ -8325,7 +8365,7 @@ Currency Cluster Risks - Currency Cluster Risks + Ryzyka koncentracji walutowej apps/client/src/app/pages/i18n/i18n-page.html 83 @@ -8333,7 +8373,7 @@ Economic Market Cluster Risks - Economic Market Cluster Risks + Ryzyka skupienia w obrębie segmentów rynku gospodarczego apps/client/src/app/pages/i18n/i18n-page.html 106 @@ -8341,7 +8381,7 @@ Emergency Fund - Emergency Fund + Fundusz Awaryjny apps/client/src/app/pages/i18n/i18n-page.html 144 @@ -8349,7 +8389,7 @@ Fees - Fees + Opłaty apps/client/src/app/pages/i18n/i18n-page.html 161 @@ -8357,7 +8397,7 @@ Liquidity - Liquidity + Płynność apps/client/src/app/pages/i18n/i18n-page.html 70 @@ -8365,7 +8405,7 @@ Buying Power - Buying Power + Siła nabywcza apps/client/src/app/pages/i18n/i18n-page.html 71 @@ -8373,7 +8413,7 @@ Your buying power is below ${thresholdMin} ${baseCurrency} - Your buying power is below ${thresholdMin} ${baseCurrency} + Twoja siła nabywcza jest niższa niż ${thresholdMin} ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 73 @@ -8381,7 +8421,7 @@ Your buying power is 0 ${baseCurrency} - Your buying power is 0 ${baseCurrency} + Twoja siła nabywcza to 0 ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 77 @@ -8389,7 +8429,7 @@ Your buying power exceeds ${thresholdMin} ${baseCurrency} - Your buying power exceeds ${thresholdMin} ${baseCurrency} + Twoja siła nabywcza przekracza ${thresholdMin} ${baseCurrency} apps/client/src/app/pages/i18n/i18n-page.html 80 @@ -8397,7 +8437,7 @@ Regional Market Cluster Risks - Regional Market Cluster Risks + Ryzyka skupienia w obrębie regionów rynkowych apps/client/src/app/pages/i18n/i18n-page.html 163 @@ -8405,7 +8445,7 @@ No results found... - No results found... + Nie znaleziono wyników... libs/ui/src/lib/assistant/assistant.html 51 @@ -8413,7 +8453,7 @@ Developed Markets - Developed Markets + Rynki rozwinięte apps/client/src/app/pages/i18n/i18n-page.html 109 @@ -8421,7 +8461,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) exceeds ${thresholdMax}% - The developed markets contribution of your current investment (${developedMarketsValueRatio}%) exceeds ${thresholdMax}% + Udział rynków rozwiniętych w Twojej obecnej inwestycji (${developedMarketsValueRatio}%) przekracza ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 112 @@ -8429,7 +8469,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is below ${thresholdMin}% - The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is below ${thresholdMin}% + Udział rynków rozwiniętych w Twojej obecnej inwestycji (${developedMarketsValueRatio}%) jest poniżej ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 117 @@ -8437,7 +8477,7 @@ The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The developed markets contribution of your current investment (${developedMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + Udział rynków rozwiniętych w Twojej obecnej inwestycji (${developedMarketsValueRatio}%) mieści się w zakresie ${thresholdMin}% i ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 122 @@ -8445,7 +8485,7 @@ Emerging Markets - Emerging Markets + Rynki wschodzące apps/client/src/app/pages/i18n/i18n-page.html 127 @@ -8453,7 +8493,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) exceeds ${thresholdMax}% - The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) exceeds ${thresholdMax}% + Udział rynków wschodzących w obecnej inwestycji (${emergingMarketsValueRatio}%) przekracza ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 130 @@ -8461,7 +8501,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is below ${thresholdMin}% - The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is below ${thresholdMin}% + Udział rynków wschodzących w obecnej inwestycji (${emergingMarketsValueRatio}%) jest poniżej ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 135 @@ -8469,7 +8509,7 @@ The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The emerging markets contribution of your current investment (${emergingMarketsValueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + Udział rynków wschodzących w obecnej inwestycji (${emergingMarketsValueRatio}%) mieści się w zakresie ${thresholdMin}% i ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 140 @@ -8477,7 +8517,7 @@ No accounts have been set up - No accounts have been set up + Nie utworzono żadnych kont apps/client/src/app/pages/i18n/i18n-page.html 21 @@ -8485,7 +8525,7 @@ Your net worth is managed by 0 accounts - Your net worth is managed by 0 accounts + Twój majątek jest zarządzany przez 0 kont apps/client/src/app/pages/i18n/i18n-page.html 33 @@ -8493,7 +8533,7 @@ Asia-Pacific - Asia-Pacific + Asia-Pacific apps/client/src/app/pages/i18n/i18n-page.html 165 @@ -8501,7 +8541,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The Asia-Pacific market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + Udział rynku Azji i Pacyfiku w Twojej obecnej inwestycji (${valueRatio}%) przekracza ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 167 @@ -8509,7 +8549,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The Asia-Pacific market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + Udział rynku Azji i Pacyfiku w Twojej obecnej inwestycji (${valueRatio}%) jest poniżej ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 171 @@ -8517,7 +8557,7 @@ The Asia-Pacific market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The Asia-Pacific market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + Udział rynku Azji i Pacyfiku w Twojej obecnej inwestycji (${valueRatio}%) mieści się w zakresie ${thresholdMin}% i ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 175 @@ -8525,7 +8565,7 @@ Emerging Markets - Emerging Markets + Rynki wschodzące apps/client/src/app/pages/i18n/i18n-page.html 180 @@ -8533,7 +8573,7 @@ The Emerging Markets contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The Emerging Markets contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + Udział rynków wschodzących w Twojej obecnej inwestycji (${valueRatio}%) przekracza ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 183 @@ -8541,7 +8581,7 @@ The Emerging Markets contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The Emerging Markets contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + Udział rynków wschodzących w Twojej obecnej inwestycji (${valueRatio}%) jest poniżej ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 187 @@ -8549,7 +8589,7 @@ The Emerging Markets contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The Emerging Markets contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + Udział rynków wschodzących w Twojej obecnej inwestycji (${valueRatio}%) mieści się w zakresie ${thresholdMin}% i ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 191 @@ -8557,7 +8597,7 @@ Europe - Europe + Europa apps/client/src/app/pages/i18n/i18n-page.html 195 @@ -8565,7 +8605,7 @@ The Europe market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The Europe market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + Udział rynku europejskiego w obecnej inwestycji (${valueRatio}%) przekracza ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 197 @@ -8573,7 +8613,7 @@ The Europe market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The Europe market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + Udział rynku europejskiego w obecnej inwestycji (${valueRatio}%) jest poniżej ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 201 @@ -8581,7 +8621,7 @@ The Europe market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The Europe market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + Udział rynku europejskiego w obecnej inwestycji (${valueRatio}%) mieści się w zakresie ${thresholdMin}% i ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 205 @@ -8589,7 +8629,7 @@ Japan - Japan + Japonia apps/client/src/app/pages/i18n/i18n-page.html 209 @@ -8597,7 +8637,7 @@ The Japan market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The Japan market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + Udział rynku japońskiego w obecnej inwestycj (${valueRatio}%) przekracza ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 211 @@ -8605,7 +8645,7 @@ The Japan market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The Japan market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + Udział rynku japońskiego w obecnej inwestycj (${valueRatio}%) jest poniżej ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 215 @@ -8613,7 +8653,7 @@ The Japan market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The Japan market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + Udział rynku japońskiego w obecnej inwestycj (${valueRatio}%) mieści się w zakresie ${thresholdMin}% i ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 219 @@ -8621,7 +8661,7 @@ North America - North America + Północna Ameryka apps/client/src/app/pages/i18n/i18n-page.html 223 @@ -8629,7 +8669,7 @@ The North America market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% - The North America market contribution of your current investment (${valueRatio}%) exceeds ${thresholdMax}% + Udział rynku Ameryki Północnej w obecnej inwestycji (${valueRatio}%) przekracza ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 225 @@ -8637,7 +8677,7 @@ The North America market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% - The North America market contribution of your current investment (${valueRatio}%) is below ${thresholdMin}% + Udział rynku Ameryki Północnej w obecnej inwestycji (${valueRatio}%) jest poniżej ${thresholdMin}% apps/client/src/app/pages/i18n/i18n-page.html 229 @@ -8645,7 +8685,7 @@ The North America market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% - The North America market contribution of your current investment (${valueRatio}%) is within the range of ${thresholdMin}% and ${thresholdMax}% + Udział rynku Ameryki Północnej w obecnej inwestycji (${valueRatio}%) mieści się w zakresie ${thresholdMin}% i ${thresholdMax}% apps/client/src/app/pages/i18n/i18n-page.html 233 @@ -8653,7 +8693,7 @@ Find Ghostfolio on GitHub - Find Ghostfolio on GitHub + Znajdź Ghostfolio na GitHub apps/client/src/app/pages/about/overview/about-overview-page.html 99 @@ -8665,7 +8705,7 @@ Join the Ghostfolio Slack community - Join the Ghostfolio Slack community + Dołącz do społeczności Ghostfolio na Slacku apps/client/src/app/pages/about/overview/about-overview-page.html 109 @@ -8673,7 +8713,7 @@ Follow Ghostfolio on X (formerly Twitter) - Follow Ghostfolio on X (formerly Twitter) + Śledź Ghostfolio na X (poprzednio Twitter) apps/client/src/app/pages/about/overview/about-overview-page.html 118 @@ -8681,7 +8721,7 @@ Send an e-mail - Send an e-mail + Wyślij e-mail apps/client/src/app/pages/about/overview/about-overview-page.html 89 @@ -8693,15 +8733,15 @@ Registration Date - Registration Date + Data rejestracji apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 26 + 45 Follow Ghostfolio on LinkedIn - Follow Ghostfolio on LinkedIn + Śledź Ghostfolio na LinkedIn apps/client/src/app/pages/about/overview/about-overview-page.html 147 @@ -8709,7 +8749,7 @@ Ghostfolio is an independent & bootstrapped business - Ghostfolio is an independent & bootstrapped business + Ghostfolio to niezależna & samofinansująca się firma apps/client/src/app/pages/about/overview/about-overview-page.html 157 @@ -8717,7 +8757,7 @@ Support Ghostfolio - Support Ghostfolio + Wesprzyj Ghostfolio apps/client/src/app/pages/about/overview/about-overview-page.html 166 diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index 2bcd7c401..3531f84d7 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -42,7 +42,7 @@ Tipo apps/client/src/app/components/admin-jobs/admin-jobs.html - 48 + 57 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -114,7 +114,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -126,7 +126,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -142,7 +142,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 139 + 135 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -186,7 +186,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -198,7 +198,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 145 + 141 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -234,15 +234,15 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 205 + 201 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 208 + 204 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 211 + 207 libs/ui/src/lib/account-balances/account-balances.component.html @@ -290,11 +290,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -322,11 +322,15 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 libs/ui/src/lib/account-balances/account-balances.component.html @@ -350,7 +354,7 @@ Pretende realmente eliminar esta conta? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 150 + 148 @@ -358,7 +362,7 @@ Fonte de dados apps/client/src/app/components/admin-jobs/admin-jobs.html - 82 + 91 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -370,7 +374,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 155 + 151 libs/ui/src/lib/i18n.ts @@ -382,7 +386,7 @@ Tentativas apps/client/src/app/components/admin-jobs/admin-jobs.html - 120 + 129 @@ -390,7 +394,7 @@ Criado apps/client/src/app/components/admin-jobs/admin-jobs.html - 134 + 143 @@ -398,7 +402,7 @@ Terminado apps/client/src/app/components/admin-jobs/admin-jobs.html - 143 + 152 @@ -406,11 +410,11 @@ Estado apps/client/src/app/components/admin-jobs/admin-jobs.html - 152 + 161 apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -426,7 +430,7 @@ Eliminar Tarefas apps/client/src/app/components/admin-jobs/admin-jobs.html - 193 + 202 @@ -434,7 +438,7 @@ Perfil de Ativos apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -446,11 +450,11 @@ Histórico de Dados de Mercado apps/client/src/app/components/admin-jobs/admin-jobs.html - 54 + 63 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -458,7 +462,7 @@ Visualizar dados apps/client/src/app/components/admin-jobs/admin-jobs.html - 208 + 217 @@ -466,7 +470,7 @@ Ver Stacktrace apps/client/src/app/components/admin-jobs/admin-jobs.html - 216 + 225 @@ -474,7 +478,7 @@ Apagar Tarefa apps/client/src/app/components/admin-jobs/admin-jobs.html - 224 + 233 @@ -490,7 +494,7 @@ Data apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 161 + 157 libs/ui/src/lib/account-balances/account-balances.component.html @@ -526,7 +530,7 @@ Filtrar por... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 385 + 383 @@ -566,7 +570,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 44 + 40 @@ -602,7 +606,7 @@ Deseja realmente eliminar este cupão? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 193 + 196 @@ -610,7 +614,7 @@ Deseja realmente limpar a cache? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 230 + 233 @@ -618,7 +622,7 @@ Por favor, defina a sua mensagem do sistema: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 250 + 253 @@ -746,7 +750,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 89 + 108 @@ -762,11 +766,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -806,7 +810,7 @@ Referência apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -842,7 +846,7 @@ apps/client/src/app/components/header/header.component.ts - 297 + 298 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -862,15 +866,15 @@ Oops! Token de Segurança Incorreto. apps/client/src/app/components/header/header.component.ts - 312 + 313 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 154 + 152 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 193 + 191 @@ -878,7 +882,7 @@ Gerir Atividades apps/client/src/app/components/home-holdings/home-holdings.html - 67 + 64 @@ -886,11 +890,11 @@ Medo apps/client/src/app/components/home-market/home-market.component.ts - 42 + 41 apps/client/src/app/components/markets/markets.component.ts - 47 + 46 libs/ui/src/lib/i18n.ts @@ -902,11 +906,11 @@ Ganância apps/client/src/app/components/home-market/home-market.component.ts - 43 + 42 apps/client/src/app/components/markets/markets.component.ts - 48 + 47 libs/ui/src/lib/i18n.ts @@ -938,7 +942,7 @@ Depósito libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 377 + 404 @@ -946,7 +950,7 @@ Valor Total apps/client/src/app/components/investment-chart/investment-chart.component.ts - 143 + 146 @@ -1058,7 +1062,7 @@ Desempenho Bruto Absoluto apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 73 + 77 @@ -1066,7 +1070,7 @@ Desempenho Líquido Absoluto apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 107 + 111 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -1078,7 +1082,7 @@ Desempenho Líquido apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 123 + 127 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -1090,7 +1094,7 @@ Ativos Totais apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 149 + 153 @@ -1098,7 +1102,7 @@ Poder de Compra apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 241 + 248 @@ -1106,7 +1110,7 @@ Excluído da Análise apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 267 + 274 @@ -1114,7 +1118,7 @@ Valor Líquido apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 317 + 324 @@ -1122,7 +1126,7 @@ Desempenho Anual apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 331 + 338 @@ -1158,7 +1162,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 189 + 185 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1198,7 +1202,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 53 + 72 @@ -1210,7 +1214,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1218,7 +1222,7 @@ apps/client/src/app/pages/public/public-page.html - 114 + 113 @@ -1230,7 +1234,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1242,7 +1246,7 @@ Marcadores apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1258,7 +1262,7 @@ Dados do Relatório com Problema apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 451 + 456 @@ -1286,7 +1290,7 @@ Mostrar tudo libs/ui/src/lib/holdings-table/holdings-table.component.html - 216 + 212 @@ -1298,7 +1302,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 363 + 362 @@ -1306,11 +1310,11 @@ AATD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -1318,11 +1322,11 @@ 1A apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -1330,11 +1334,11 @@ 5A apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -1350,11 +1354,11 @@ Máx apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 217 libs/ui/src/lib/assistant/assistant.component.ts - 415 + 414 @@ -1378,7 +1382,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 195 + 194 @@ -1390,7 +1394,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 193 + 192 @@ -1406,7 +1410,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 196 + 195 @@ -1466,7 +1470,7 @@ Auto apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 70 + 69 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1510,7 +1514,7 @@ Deseja realmente remover este método de início de sessão? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 279 @@ -1586,7 +1590,7 @@ Localidade apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -1666,7 +1670,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 11 + 30 @@ -1714,11 +1718,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 375 + 381 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 66 + 85 apps/client/src/app/pages/accounts/accounts-page.html @@ -1886,7 +1890,7 @@ Como já tem sessão iniciada, não pode aceder à conta de demonstração. apps/client/src/app/pages/demo/demo-page.component.ts - 35 + 32 @@ -1932,6 +1936,10 @@ Overview Visão geral + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 7 + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 114 @@ -1950,11 +1958,11 @@ apps/client/src/app/pages/admin/admin-page.component.ts - 48 + 45 apps/client/src/app/pages/resources/resources-page.component.ts - 30 + 29 libs/common/src/lib/routes/routes.ts @@ -1970,7 +1978,7 @@ Mercados apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -1994,7 +2002,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 40 + 39 libs/common/src/lib/routes/routes.ts @@ -2022,7 +2030,7 @@ apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 115 + 113 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -2042,11 +2050,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 342 + 348 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 75 + 94 apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -2074,7 +2082,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 @@ -2118,7 +2126,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 124 + 120 @@ -2126,7 +2134,7 @@ Preço por Unidade apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 214 + 210 libs/ui/src/lib/activities-table/activities-table.component.html @@ -2138,7 +2146,7 @@ Nota apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -2146,7 +2154,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 275 + 271 @@ -2154,7 +2162,7 @@ A importar dados... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 175 + 174 @@ -2162,7 +2170,7 @@ A importação foi concluída apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 185 + 184 @@ -2282,7 +2290,7 @@ apps/client/src/app/pages/public/public-page.html - 151 + 150 @@ -2306,7 +2314,7 @@ apps/client/src/app/pages/public/public-page.html - 168 + 167 @@ -2314,7 +2322,7 @@ Latest activities apps/client/src/app/pages/public/public-page.html - 211 + 210 @@ -2326,7 +2334,7 @@ apps/client/src/app/pages/public/public-page.html - 177 + 176 @@ -2338,7 +2346,7 @@ apps/client/src/app/pages/public/public-page.html - 186 + 185 @@ -2358,7 +2366,7 @@ Mensalmente apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 91 + 90 @@ -2482,7 +2490,7 @@ Continentes apps/client/src/app/pages/public/public-page.html - 132 + 131 @@ -2498,7 +2506,7 @@ O Ghostfolio permite-lhe estar a par e gerir a sua riqueza. apps/client/src/app/pages/public/public-page.html - 238 + 237 @@ -2526,7 +2534,7 @@ apps/client/src/app/pages/public/public-page.html - 242 + 241 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -2580,6 +2588,10 @@ apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 88 + + libs/ui/src/lib/value/value.component.html + 18 + Resources @@ -2698,7 +2710,7 @@ Deseja realmente eliminar esta atividade? libs/ui/src/lib/activities-table/activities-table.component.ts - 288 + 292 @@ -2750,11 +2762,11 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 352 + 359 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 387 + 414 libs/ui/src/lib/i18n.ts @@ -2766,7 +2778,7 @@ Poupanças libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 397 + 424 @@ -2802,7 +2814,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2810,7 +2822,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 290 + 286 libs/ui/src/lib/i18n.ts @@ -2826,7 +2838,7 @@ Fundo de Emergência apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 164 + 168 apps/client/src/app/pages/features/features-page.html @@ -2846,7 +2858,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 437 + 451 @@ -2854,7 +2866,7 @@ Símbolo apps/client/src/app/components/admin-jobs/admin-jobs.html - 68 + 77 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -2870,7 +2882,7 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 315 + 318 libs/ui/src/lib/i18n.ts @@ -2894,7 +2906,7 @@ Dinheiro apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 212 + 219 libs/ui/src/lib/i18n.ts @@ -2942,7 +2954,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 35 + 54 @@ -3074,7 +3086,7 @@ apps/client/src/app/pages/public/public-page.html - 196 + 195 libs/ui/src/lib/benchmark/benchmark.component.html @@ -3082,11 +3094,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 439 + 453 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 452 + 467 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -3114,7 +3126,7 @@ Mapeamento de Símbolo apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -3130,7 +3142,7 @@ Dados de Mercado apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 398 + 403 libs/common/src/lib/routes/routes.ts @@ -3154,7 +3166,7 @@ A validar dados... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 299 + 293 @@ -3170,7 +3182,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 71 + 67 @@ -3194,7 +3206,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 365 + 372 apps/client/src/app/pages/features/features-page.html @@ -3202,11 +3214,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 202 + 198 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 76 + 75 libs/ui/src/lib/i18n.ts @@ -3234,7 +3246,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -3242,7 +3254,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 309 + 305 libs/ui/src/lib/i18n.ts @@ -3274,7 +3286,7 @@ Anualmente apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 91 @@ -3282,7 +3294,7 @@ Importar Dividendos apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 137 + 136 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3298,7 +3310,7 @@ Válido até apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -3337,6 +3349,14 @@ 22 + + No Activities + No Activities + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 145 + + Retirement Provision Provisão de Reforma @@ -3610,11 +3630,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -3634,7 +3654,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 88 + 92 @@ -3798,7 +3818,7 @@ Deseja mesmo eliminar estas atividades? libs/ui/src/lib/activities-table/activities-table.component.ts - 278 + 282 @@ -3809,6 +3829,14 @@ 306 + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 + + By By @@ -3830,7 +3858,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 @@ -3846,11 +3874,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -3866,7 +3894,7 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -3874,7 +3902,7 @@ Deseja mesmo eliminar esta plataforma? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 111 @@ -3882,7 +3910,7 @@ Plataformas apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -3890,7 +3918,7 @@ Atualizar saldo em Dinheiro apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 112 + 108 @@ -4070,7 +4098,7 @@ Responsabilidades apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 295 + 302 apps/client/src/app/pages/features/features-page.html @@ -4226,7 +4254,7 @@ Configuração do raspador apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -4470,7 +4498,7 @@ ETFs sem países apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 136 + 135 @@ -4478,7 +4506,7 @@ ETFs sem setores apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 141 + 140 @@ -4486,7 +4514,7 @@ Ativos apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 226 + 233 @@ -4658,7 +4686,7 @@ Job ID apps/client/src/app/components/admin-jobs/admin-jobs.html - 34 + 43 @@ -4714,11 +4742,11 @@ Moedas apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 131 + 130 apps/client/src/app/pages/public/public-page.html - 96 + 95 @@ -4766,11 +4794,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -5432,7 +5460,7 @@ Taxa apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 262 + 258 libs/ui/src/lib/activities-table/activities-table.component.html @@ -5464,7 +5492,7 @@ Você realmente deseja excluir esta tag? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 108 @@ -5532,7 +5560,7 @@ Associação apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 48 + 67 libs/common/src/lib/routes/routes.ts @@ -5564,7 +5592,7 @@ Perfil de ativos apps/client/src/app/components/admin-jobs/admin-jobs.html - 52 + 61 @@ -5688,7 +5716,7 @@ Ops! Não foi possível analisar os dados históricos. libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts - 262 + 284 @@ -5696,7 +5724,7 @@ Você realmente deseja excluir esta mensagem do sistema? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 206 + 209 @@ -5720,7 +5748,7 @@ Saldos de caixa apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 148 + 146 @@ -5740,7 +5768,7 @@ Você realmente deseja excluir o saldo desta conta? libs/ui/src/lib/account-balances/account-balances.component.ts - 120 + 113 @@ -5756,7 +5784,7 @@ O preço de mercado atual é apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5764,7 +5792,7 @@ Teste apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5852,11 +5880,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 81 + 80 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 97 + 96 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5876,7 +5904,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 447 @@ -5916,7 +5944,7 @@ Semana até agora libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5924,11 +5952,11 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5936,7 +5964,7 @@ Do mês até a data libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5944,11 +5972,11 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5956,7 +5984,7 @@ No acumulado do ano libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -5992,7 +6020,7 @@ ano apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6004,7 +6032,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -6012,11 +6040,11 @@ anos apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -6032,7 +6060,7 @@ Coleta de dados apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6044,7 +6072,7 @@ geral apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6052,7 +6080,7 @@ Nuvem apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6064,7 +6092,7 @@ Auto-hospedagem apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6105,7 +6133,7 @@ Ativo apps/client/src/app/components/home-holdings/home-holdings.component.ts - 64 + 63 @@ -6113,7 +6141,7 @@ Fechado apps/client/src/app/components/home-holdings/home-holdings.component.ts - 65 + 64 @@ -6145,7 +6173,7 @@ Executar trabalho apps/client/src/app/components/admin-jobs/admin-jobs.html - 220 + 229 @@ -6153,7 +6181,7 @@ Prioridade apps/client/src/app/components/admin-jobs/admin-jobs.html - 96 + 105 @@ -6209,7 +6237,7 @@ Você realmente deseja encerrar sua conta Ghostfolio? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 205 @@ -6257,7 +6285,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6265,7 +6293,7 @@ Ops! Ocorreu um erro ao configurar a autenticação biométrica. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 336 + 333 @@ -6281,7 +6309,7 @@ Referências apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 126 + 125 @@ -6329,7 +6357,7 @@ Você gostaria de refinar seu estratégia de investimento pessoal? apps/client/src/app/pages/public/public-page.html - 234 + 233 @@ -6661,7 +6689,7 @@ Erro apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6713,7 +6741,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6745,7 +6773,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 345 + 341 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -6753,7 +6781,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 46 + 47 libs/ui/src/lib/i18n.ts @@ -6765,7 +6793,7 @@ Fechar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6791,13 +6819,17 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 68 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 127 + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 107 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 347 + 343 libs/ui/src/lib/i18n.ts @@ -6809,7 +6841,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 14 + 33 @@ -6833,7 +6865,7 @@ Visão geral do portfólio apps/client/src/app/components/admin-jobs/admin-jobs.html - 56 + 65 @@ -6900,6 +6932,14 @@ 42 + + has been copied to the clipboard + has been copied to the clipboard + + libs/ui/src/lib/value/value.component.ts + 180 + + From the beginning Desde o início @@ -7065,7 +7105,7 @@ Definir chave de API apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7101,7 +7141,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 45 + 44 libs/common/src/lib/routes/routes.ts @@ -7117,7 +7157,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 34 + 33 libs/common/src/lib/routes/routes.ts @@ -7171,7 +7211,7 @@ de apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7179,7 +7219,7 @@ solicitações diárias apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7187,7 +7227,7 @@ Remover chave de API apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7195,7 +7235,7 @@ Você realmente deseja excluir a chave de API? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7215,7 +7255,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 98 + 117 @@ -7287,7 +7327,7 @@ Guardar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7323,11 +7363,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 356 + 352 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 48 + 49 @@ -7339,7 +7379,7 @@ apps/client/src/app/components/user-account-access/user-account-access.component.ts - 260 + 257 @@ -7363,7 +7403,7 @@ Please enter your Ghostfolio API key. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7379,7 +7419,7 @@ AI prompt has been copied to the clipboard apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 201 + 199 @@ -7395,7 +7435,7 @@ Lazy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7403,7 +7443,7 @@ Instant apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7411,7 +7451,7 @@ Preço de mercado padrão apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7419,7 +7459,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7427,7 +7467,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7435,7 +7475,7 @@ HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7443,7 +7483,7 @@ end of day apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7451,7 +7491,7 @@ real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7459,7 +7499,7 @@ Open Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 202 + 200 @@ -7479,7 +7519,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 @@ -7491,7 +7531,7 @@ apps/client/src/app/components/home-overview/home-overview.component.ts - 55 + 54 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -7499,11 +7539,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 381 + 390 @@ -7607,11 +7647,11 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 235 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 170 + 167 @@ -7619,7 +7659,7 @@ Do you really want to generate a new security token for this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 241 + 240 @@ -7627,7 +7667,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 151 + 115 @@ -7692,7 +7732,7 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7700,7 +7740,7 @@ An error occurred while updating to (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7764,7 +7804,7 @@ someone apps/client/src/app/pages/public/public-page.component.ts - 59 + 61 @@ -7796,7 +7836,7 @@ Do you really want to delete this item? libs/ui/src/lib/benchmark/benchmark.component.ts - 144 + 139 @@ -7837,7 +7877,7 @@ Demo user account has been synced. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 274 + 277 @@ -7872,7 +7912,7 @@ 150 - + Fee Ratio Fee Ratio @@ -7880,17 +7920,17 @@ 152 - - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) + + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 - - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) + + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -8051,7 +8091,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 @@ -8059,11 +8099,11 @@ new apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts - 56 + 53 @@ -8220,7 +8260,7 @@ Do you really want to generate a new security token? apps/client/src/app/components/user-account-access/user-account-access.component.ts - 175 + 172 @@ -8244,7 +8284,7 @@ Stocks apps/client/src/app/components/markets/markets.component.ts - 52 + 51 apps/client/src/app/pages/features/features-page.html @@ -8256,7 +8296,7 @@ Criptomoedas apps/client/src/app/components/markets/markets.component.ts - 53 + 52 apps/client/src/app/pages/features/features-page.html @@ -8276,7 +8316,7 @@ Gerenciar perfil de ativos apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 466 + 471 @@ -8696,7 +8736,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 26 + 45 diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index 421ef3855..e4547a2de 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -223,7 +223,7 @@ Tip apps/client/src/app/components/admin-jobs/admin-jobs.html - 48 + 57 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -311,7 +311,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -323,7 +323,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -339,7 +339,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 139 + 135 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -383,7 +383,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -395,7 +395,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 145 + 141 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -415,15 +415,15 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 205 + 201 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 208 + 204 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 211 + 207 libs/ui/src/lib/account-balances/account-balances.component.html @@ -471,11 +471,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -503,11 +503,15 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 libs/ui/src/lib/account-balances/account-balances.component.html @@ -531,7 +535,7 @@ Bu hesabı silmeyi gerçekten istiyor musunuz? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 150 + 148 @@ -539,7 +543,7 @@ Veri Kaynağı apps/client/src/app/components/admin-jobs/admin-jobs.html - 82 + 91 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -551,7 +555,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 155 + 151 libs/ui/src/lib/i18n.ts @@ -563,7 +567,7 @@ Deneme apps/client/src/app/components/admin-jobs/admin-jobs.html - 120 + 129 @@ -571,7 +575,7 @@ Oluşturuldu apps/client/src/app/components/admin-jobs/admin-jobs.html - 134 + 143 @@ -579,7 +583,7 @@ Tamamlandı apps/client/src/app/components/admin-jobs/admin-jobs.html - 143 + 152 @@ -587,11 +591,11 @@ Durum apps/client/src/app/components/admin-jobs/admin-jobs.html - 152 + 161 apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -607,7 +611,7 @@ İşleri Sil apps/client/src/app/components/admin-jobs/admin-jobs.html - 193 + 202 @@ -615,7 +619,7 @@ Varlık Profili apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -627,11 +631,11 @@ Tarihsel Piyasa Verisi apps/client/src/app/components/admin-jobs/admin-jobs.html - 54 + 63 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -639,7 +643,7 @@ Veri Gör apps/client/src/app/components/admin-jobs/admin-jobs.html - 208 + 217 @@ -647,7 +651,7 @@ Hata İzini Görüntüle apps/client/src/app/components/admin-jobs/admin-jobs.html - 216 + 225 @@ -655,7 +659,7 @@ İşleri Sil apps/client/src/app/components/admin-jobs/admin-jobs.html - 224 + 233 @@ -671,7 +675,7 @@ Tarih apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 161 + 157 libs/ui/src/lib/account-balances/account-balances.component.html @@ -707,11 +711,11 @@ Para Birimleri apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 131 + 130 apps/client/src/app/pages/public/public-page.html - 96 + 95 @@ -719,7 +723,7 @@ Ülkesi Olmayan ETF’ler apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 136 + 135 @@ -727,7 +731,7 @@ Sektörü Olmayan ETF’ler apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 141 + 140 @@ -735,7 +739,7 @@ Filtrele... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 385 + 383 @@ -775,7 +779,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 44 + 40 @@ -843,7 +847,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 53 + 72 @@ -855,7 +859,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -863,7 +867,7 @@ apps/client/src/app/pages/public/public-page.html - 114 + 113 @@ -875,7 +879,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -887,7 +891,7 @@ Sembol Eşleştirme apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -903,7 +907,7 @@ Veri Toplayıcı Yapılandırması apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -911,7 +915,7 @@ Not apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -919,7 +923,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 275 + 271 @@ -947,7 +951,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 124 + 120 @@ -955,7 +959,7 @@ Bu kuponu gerçekten silmek istiyor musunuz? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 193 + 196 @@ -963,7 +967,7 @@ Önbelleği temizlemeyi gerçekten istiyor musunuz? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 230 + 233 @@ -971,7 +975,7 @@ Lütfen sistem mesajınızı belirleyin: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 250 + 253 @@ -1011,7 +1015,7 @@ Etiketler apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1103,11 +1107,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1123,7 +1127,7 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -1131,7 +1135,15 @@ Bu platformu silmeyi gerçekten istiyor musunuz? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 111 + + + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 @@ -1155,7 +1167,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 @@ -1171,7 +1183,7 @@ Platformlar apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -1215,7 +1227,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 89 + 108 @@ -1247,11 +1259,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -1299,7 +1311,7 @@ Karşılaştırma Ölçütü apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1335,7 +1347,7 @@ apps/client/src/app/components/header/header.component.ts - 297 + 298 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -1355,15 +1367,15 @@ Hay Allah! Güvenlik anahtarı yanlış. apps/client/src/app/components/header/header.component.ts - 312 + 313 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 154 + 152 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 193 + 191 @@ -1371,7 +1383,7 @@ İşlemleri Yönet apps/client/src/app/components/home-holdings/home-holdings.html - 67 + 64 @@ -1379,11 +1391,11 @@ Korku apps/client/src/app/components/home-market/home-market.component.ts - 42 + 41 apps/client/src/app/components/markets/markets.component.ts - 47 + 46 libs/ui/src/lib/i18n.ts @@ -1395,11 +1407,11 @@ Açgözlülük apps/client/src/app/components/home-market/home-market.component.ts - 43 + 42 apps/client/src/app/components/markets/markets.component.ts - 48 + 47 libs/ui/src/lib/i18n.ts @@ -1503,7 +1515,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 @@ -1523,7 +1535,7 @@ Toplam Tutar apps/client/src/app/components/investment-chart/investment-chart.component.ts - 143 + 146 @@ -1635,7 +1647,7 @@ Toplam Brüt Performans apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 73 + 77 @@ -1643,7 +1655,7 @@ Toplam Net Performans apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 107 + 111 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -1655,7 +1667,7 @@ Net Performans apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 123 + 127 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -1667,7 +1679,7 @@ Toplam Varlıklar apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 149 + 153 @@ -1675,7 +1687,7 @@ Varlıklar apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 226 + 233 @@ -1683,7 +1695,7 @@ Alım Limiti apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 241 + 248 @@ -1691,7 +1703,7 @@ Analize Dahil Edilmemiştir. apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 267 + 274 @@ -1699,7 +1711,7 @@ Yükümlülükler apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 295 + 302 apps/client/src/app/pages/features/features-page.html @@ -1711,7 +1723,7 @@ Toplam Varlık apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 317 + 324 @@ -1719,7 +1731,7 @@ Yıllıklandırılmış Performans apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 331 + 338 @@ -1755,7 +1767,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 189 + 185 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1775,7 +1787,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 88 + 92 @@ -1783,7 +1795,7 @@ Rapor Veri Sorunu apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 451 + 456 @@ -1963,7 +1975,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 363 + 362 @@ -1971,11 +1983,11 @@ YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -1983,11 +1995,11 @@ 1Y apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -1995,11 +2007,11 @@ 5Y apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -2015,11 +2027,11 @@ Maks. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 217 libs/ui/src/lib/assistant/assistant.component.ts - 415 + 414 @@ -2043,7 +2055,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 195 + 194 @@ -2055,7 +2067,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 193 + 192 @@ -2071,7 +2083,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 196 + 195 @@ -2199,11 +2211,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 375 + 381 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 66 + 85 apps/client/src/app/pages/accounts/accounts-page.html @@ -2263,7 +2275,7 @@ Piyasa Verileri apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 398 + 403 libs/common/src/lib/routes/routes.ts @@ -2297,6 +2309,10 @@ Overview Özet + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 7 + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 114 @@ -2315,11 +2331,11 @@ apps/client/src/app/pages/admin/admin-page.component.ts - 48 + 45 apps/client/src/app/pages/resources/resources-page.component.ts - 30 + 29 libs/common/src/lib/routes/routes.ts @@ -2443,11 +2459,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -2463,7 +2479,7 @@ Oturum açmış olduğunuz için demo hesabına erişemezsiniz. apps/client/src/app/pages/demo/demo-page.component.ts - 35 + 32 @@ -2647,7 +2663,7 @@ apps/client/src/app/pages/public/public-page.html - 242 + 241 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -2699,7 +2715,7 @@ Piyasalar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -2723,7 +2739,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 40 + 39 libs/common/src/lib/routes/routes.ts @@ -3087,7 +3103,7 @@ Job ID apps/client/src/app/components/admin-jobs/admin-jobs.html - 34 + 43 @@ -3115,7 +3131,7 @@ apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 115 + 113 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -3135,11 +3151,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 342 + 348 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 75 + 94 apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -3159,7 +3175,7 @@ Tüm işlemlerinizi silmeyi gerçekten istiyor musunuz? libs/ui/src/lib/activities-table/activities-table.component.ts - 278 + 282 @@ -3203,7 +3219,7 @@ Nakit Bakiyesini Güncelle apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 112 + 108 @@ -3211,7 +3227,7 @@ Birim Fiyat apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 214 + 210 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3239,7 +3255,7 @@ Temettüleri İçe Aktar apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 137 + 136 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3255,7 +3271,7 @@ Veri içe aktarılıyor... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 175 + 174 @@ -3263,7 +3279,7 @@ İçe aktarma tamamlandı apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 185 + 184 @@ -3279,7 +3295,7 @@ Veri doğrulanıyor... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 299 + 293 @@ -3367,7 +3383,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 71 + 67 @@ -3459,7 +3475,7 @@ apps/client/src/app/pages/public/public-page.html - 151 + 150 @@ -3483,7 +3499,7 @@ apps/client/src/app/pages/public/public-page.html - 168 + 167 @@ -3491,7 +3507,7 @@ Latest activities apps/client/src/app/pages/public/public-page.html - 211 + 210 @@ -3503,7 +3519,7 @@ apps/client/src/app/pages/public/public-page.html - 177 + 176 @@ -3515,7 +3531,7 @@ apps/client/src/app/pages/public/public-page.html - 186 + 185 @@ -3575,7 +3591,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 365 + 372 apps/client/src/app/pages/features/features-page.html @@ -3583,11 +3599,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 202 + 198 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 76 + 75 libs/ui/src/lib/i18n.ts @@ -3607,7 +3623,7 @@ Para Yatırma libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 377 + 404 @@ -3615,7 +3631,7 @@ Aylık apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 91 + 90 @@ -3623,7 +3639,7 @@ Yıllık apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 91 @@ -3903,11 +3919,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -3931,7 +3947,7 @@ Kıtalar apps/client/src/app/pages/public/public-page.html - 132 + 131 @@ -3947,7 +3963,7 @@ Ghostfolio, varlıklarınızı takip etmenizi sağlar. apps/client/src/app/pages/public/public-page.html - 238 + 237 @@ -3997,6 +4013,10 @@ apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 88 + + libs/ui/src/lib/value/value.component.html + 18 + Personal Finance Tools @@ -4336,7 +4356,7 @@ Otomatik apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 70 + 69 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -4380,7 +4400,7 @@ Bu giriş yöntemini kaldırmayı gerçekten istiyor musunuz? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 279 @@ -4388,7 +4408,7 @@ Geçerli tarih apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -4468,7 +4488,7 @@ Yerel Ayarlar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -4568,7 +4588,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 11 + 30 @@ -4664,7 +4684,7 @@ TBu işlemi silmeyi gerçekten istiyor musunuz? libs/ui/src/lib/activities-table/activities-table.component.ts - 288 + 292 @@ -4740,11 +4760,11 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 352 + 359 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 387 + 414 libs/ui/src/lib/i18n.ts @@ -4756,7 +4776,7 @@ Tasarruflar libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 397 + 424 @@ -4784,7 +4804,7 @@ Tümünü göster libs/ui/src/lib/holdings-table/holdings-table.component.html - 216 + 212 @@ -4828,7 +4848,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -4836,7 +4856,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 290 + 286 libs/ui/src/lib/i18n.ts @@ -4860,7 +4880,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -4868,7 +4888,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 309 + 305 libs/ui/src/lib/i18n.ts @@ -4912,7 +4932,7 @@ Acil Durum Fonu apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 164 + 168 apps/client/src/app/pages/features/features-page.html @@ -4988,7 +5008,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 437 + 451 @@ -4999,6 +5019,14 @@ 27 + + No Activities + No Activities + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 145 + + Retirement Provision Yaşlılık Provizyonu @@ -5028,7 +5056,7 @@ Sembol apps/client/src/app/components/admin-jobs/admin-jobs.html - 68 + 77 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -5044,7 +5072,7 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 315 + 318 libs/ui/src/lib/i18n.ts @@ -5144,7 +5172,7 @@ Nakit apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 212 + 219 libs/ui/src/lib/i18n.ts @@ -5192,7 +5220,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 35 + 54 @@ -5324,7 +5352,7 @@ apps/client/src/app/pages/public/public-page.html - 196 + 195 libs/ui/src/lib/benchmark/benchmark.component.html @@ -5332,11 +5360,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 439 + 453 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 452 + 467 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -5440,7 +5468,7 @@ Ücret apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 262 + 258 libs/ui/src/lib/activities-table/activities-table.component.html @@ -5464,7 +5492,7 @@ Bu etiketi silmeyi gerçekten istiyor musunuz? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 108 @@ -5532,7 +5560,7 @@ Üyelik apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 48 + 67 libs/common/src/lib/routes/routes.ts @@ -5564,7 +5592,7 @@ Varlık Profili apps/client/src/app/components/admin-jobs/admin-jobs.html - 52 + 61 @@ -5688,7 +5716,7 @@ Hay Allah! Geçmiş veriler ayrıştırılamadı. libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts - 262 + 284 @@ -5696,7 +5724,7 @@ Bu sistem mesajını silmeyi gerçekten istiyor musunuz? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 206 + 209 @@ -5720,7 +5748,7 @@ Nakit Bakiyeleri apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 148 + 146 @@ -5740,7 +5768,7 @@ Bu nakit bakiyesini silmeyi gerçekten istiyor musunuz? libs/ui/src/lib/account-balances/account-balances.component.ts - 120 + 113 @@ -5756,7 +5784,7 @@ Şu anki piyasa fiyatı apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5764,7 +5792,7 @@ Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5852,11 +5880,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 81 + 80 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 97 + 96 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5876,7 +5904,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 447 @@ -5916,7 +5944,7 @@ Hafta içi libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5924,11 +5952,11 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5936,7 +5964,7 @@ Ay içi libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5944,11 +5972,11 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5956,7 +5984,7 @@ Yıl içi libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -5992,7 +6020,7 @@ Yıl apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6004,7 +6032,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -6012,11 +6040,11 @@ Yıllar apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -6032,7 +6060,7 @@ Veri Toplama apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6044,7 +6072,7 @@ Genel apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6052,7 +6080,7 @@ Bulut apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6064,7 +6092,7 @@ Kendini Barındırma apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6105,7 +6133,7 @@ Aktif apps/client/src/app/components/home-holdings/home-holdings.component.ts - 64 + 63 @@ -6113,7 +6141,7 @@ Kapalı apps/client/src/app/components/home-holdings/home-holdings.component.ts - 65 + 64 @@ -6145,7 +6173,7 @@ İşlemi Yürüt apps/client/src/app/components/admin-jobs/admin-jobs.html - 220 + 229 @@ -6153,7 +6181,7 @@ Öncelik apps/client/src/app/components/admin-jobs/admin-jobs.html - 96 + 105 @@ -6209,7 +6237,7 @@ Ghostfolio hesabınızı kapatmak istediğinize emin misiniz? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 205 @@ -6257,7 +6285,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6265,7 +6293,7 @@ Oops! Biyometrik kimlik doğrulama ayarlanırken bir hata oluştu. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 336 + 333 @@ -6281,7 +6309,7 @@ Kıyaslamalar apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 126 + 125 @@ -6329,7 +6357,7 @@ Senin özel yatırım stratejinizi iyileştirmek ister misin? apps/client/src/app/pages/public/public-page.html - 234 + 233 @@ -6661,7 +6689,7 @@ Hata apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6713,7 +6741,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6745,7 +6773,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 345 + 341 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -6753,7 +6781,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 46 + 47 libs/ui/src/lib/i18n.ts @@ -6765,7 +6793,7 @@ Kapat apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6791,13 +6819,17 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 68 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 127 + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 107 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 347 + 343 libs/ui/src/lib/i18n.ts @@ -6809,7 +6841,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 14 + 33 @@ -6833,7 +6865,7 @@ Portföy Anlık Görüntüsü apps/client/src/app/components/admin-jobs/admin-jobs.html - 56 + 65 @@ -6900,6 +6932,14 @@ 42 + + has been copied to the clipboard + has been copied to the clipboard + + libs/ui/src/lib/value/value.component.ts + 180 + + From the beginning Başlangıçtan beri @@ -7065,7 +7105,7 @@ API anahtarını ayarla apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7101,7 +7141,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 45 + 44 libs/common/src/lib/routes/routes.ts @@ -7117,7 +7157,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 34 + 33 libs/common/src/lib/routes/routes.ts @@ -7171,7 +7211,7 @@ ın apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7179,7 +7219,7 @@ günlük istekler apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7187,7 +7227,7 @@ API anahtarını kaldır apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7195,7 +7235,7 @@ API anahtarını silmek istediğinize emin misiniz? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7215,7 +7255,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 98 + 117 @@ -7287,7 +7327,7 @@ Kaydet apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7323,11 +7363,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 356 + 352 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 48 + 49 @@ -7339,7 +7379,7 @@ apps/client/src/app/components/user-account-access/user-account-access.component.ts - 260 + 257 @@ -7363,7 +7403,7 @@ Lütfen Ghostfolio API anahtarınızı girin. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7379,7 +7419,7 @@ Yapay zeka istemi panoya kopyalandı apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 201 + 199 @@ -7395,7 +7435,7 @@ Tembel apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7403,7 +7443,7 @@ Anında apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7411,7 +7451,7 @@ Varsayılan Piyasa Fiyatı apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7419,7 +7459,7 @@ Mod apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7427,7 +7467,7 @@ Seçici apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7435,7 +7475,7 @@ HTTP İstek Başlıkları apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7443,7 +7483,7 @@ gün sonu apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7451,7 +7491,7 @@ gerçek zamanlı apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7459,7 +7499,7 @@ Duck.ai’yi aç apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 202 + 200 @@ -7479,7 +7519,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 @@ -7491,7 +7531,7 @@ apps/client/src/app/components/home-overview/home-overview.component.ts - 55 + 54 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -7499,11 +7539,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 381 + 390 @@ -7607,11 +7647,11 @@ Güvenlik belirteci apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 235 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 170 + 167 @@ -7619,7 +7659,7 @@ Bu kullanıcı için yeni bir güvenlik belirteci oluşturmak istediğinize emin misiniz? apps/client/src/app/components/admin-users/admin-users.component.ts - 241 + 240 @@ -7627,7 +7667,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 151 + 115 @@ -7692,7 +7732,7 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7700,7 +7740,7 @@ Güncelleştirilirken bir hata oluştu (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7764,7 +7804,7 @@ birisi apps/client/src/app/pages/public/public-page.component.ts - 59 + 61 @@ -7796,7 +7836,7 @@ Bu öğeyi silmek istediğinize emin misiniz? libs/ui/src/lib/benchmark/benchmark.component.ts - 144 + 139 @@ -7837,7 +7877,7 @@ Demo kullanıcı hesabı senkronize edildi. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 274 + 277 @@ -7872,25 +7912,25 @@ 150 - + Fee Ratio - Ücret Oranı + Fee Ratio apps/client/src/app/pages/i18n/i18n-page.html 152 - - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - Ücretler, ilk yatırımınızın %${thresholdMax} kadarını aşıyor (${feeRatio}%) + + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 - - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - Ücretler, ilk yatırımınızın %${thresholdMax}’ını (${feeRatio}%) aşmaz + + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -8051,7 +8091,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 @@ -8059,11 +8099,11 @@ yeni apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts - 56 + 53 @@ -8220,7 +8260,7 @@ Do you really want to generate a new security token? apps/client/src/app/components/user-account-access/user-account-access.component.ts - 175 + 172 @@ -8244,7 +8284,7 @@ Stocks apps/client/src/app/components/markets/markets.component.ts - 52 + 51 apps/client/src/app/pages/features/features-page.html @@ -8256,7 +8296,7 @@ Cryptocurrencies apps/client/src/app/components/markets/markets.component.ts - 53 + 52 apps/client/src/app/pages/features/features-page.html @@ -8276,7 +8316,7 @@ Manage Asset Profile apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 466 + 471 @@ -8696,7 +8736,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 26 + 45 diff --git a/apps/client/src/locales/messages.uk.xlf b/apps/client/src/locales/messages.uk.xlf index 2fc389030..12b5c2bc3 100644 --- a/apps/client/src/locales/messages.uk.xlf +++ b/apps/client/src/locales/messages.uk.xlf @@ -38,7 +38,7 @@ apps/client/src/app/components/header/header.component.ts - 297 + 298 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -419,7 +419,7 @@ Баланс готівки apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 148 + 146 @@ -443,7 +443,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -455,7 +455,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -471,7 +471,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 139 + 135 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -515,7 +515,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -527,7 +527,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 145 + 141 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -547,15 +547,15 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 205 + 201 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 208 + 204 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 211 + 207 libs/ui/src/lib/account-balances/account-balances.component.html @@ -603,11 +603,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -635,11 +635,15 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 libs/ui/src/lib/account-balances/account-balances.component.html @@ -663,7 +667,7 @@ Ви дійсно хочете видалити цей обліковий запис? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 150 + 148 @@ -671,7 +675,7 @@ Тип apps/client/src/app/components/admin-jobs/admin-jobs.html - 48 + 57 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -691,7 +695,7 @@ Профіль активу apps/client/src/app/components/admin-jobs/admin-jobs.html - 52 + 61 @@ -699,11 +703,11 @@ Історичні ринкові дані apps/client/src/app/components/admin-jobs/admin-jobs.html - 54 + 63 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -711,7 +715,7 @@ Знімок портфеля apps/client/src/app/components/admin-jobs/admin-jobs.html - 56 + 65 @@ -719,7 +723,7 @@ Джерело даних apps/client/src/app/components/admin-jobs/admin-jobs.html - 82 + 91 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -731,7 +735,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 155 + 151 libs/ui/src/lib/i18n.ts @@ -743,7 +747,7 @@ Пріоритет apps/client/src/app/components/admin-jobs/admin-jobs.html - 96 + 105 @@ -751,7 +755,7 @@ Спроби apps/client/src/app/components/admin-jobs/admin-jobs.html - 120 + 129 @@ -759,7 +763,7 @@ Створено apps/client/src/app/components/admin-jobs/admin-jobs.html - 134 + 143 @@ -767,7 +771,7 @@ Завершено apps/client/src/app/components/admin-jobs/admin-jobs.html - 143 + 152 @@ -775,11 +779,11 @@ Статус apps/client/src/app/components/admin-jobs/admin-jobs.html - 152 + 161 apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -795,7 +799,7 @@ Видалити завдання apps/client/src/app/components/admin-jobs/admin-jobs.html - 193 + 202 @@ -803,7 +807,7 @@ Переглянути дані apps/client/src/app/components/admin-jobs/admin-jobs.html - 208 + 217 @@ -811,7 +815,7 @@ Переглянути трасування apps/client/src/app/components/admin-jobs/admin-jobs.html - 216 + 225 @@ -819,7 +823,7 @@ Виконати завдання apps/client/src/app/components/admin-jobs/admin-jobs.html - 220 + 229 @@ -827,7 +831,7 @@ Видалити завдання apps/client/src/app/components/admin-jobs/admin-jobs.html - 224 + 233 @@ -835,7 +839,7 @@ Порівняльні показники apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 126 + 125 @@ -843,11 +847,11 @@ Валюти apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 131 + 130 apps/client/src/app/pages/public/public-page.html - 96 + 95 @@ -855,7 +859,7 @@ ETF без країн apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 136 + 135 @@ -863,7 +867,7 @@ ETF без секторів apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 141 + 140 @@ -871,7 +875,7 @@ Фільтрувати за... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 385 + 383 @@ -927,7 +931,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 44 + 40 @@ -995,7 +999,7 @@ Помилка apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -1003,7 +1007,7 @@ Поточна ринкова ціна apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -1043,7 +1047,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 53 + 72 @@ -1055,7 +1059,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1063,7 +1067,7 @@ apps/client/src/app/pages/public/public-page.html - 114 + 113 @@ -1075,7 +1079,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1087,7 +1091,7 @@ Зіставлення символів apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -1103,7 +1107,7 @@ Конфігурація скребка apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -1111,7 +1115,7 @@ Тест apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -1119,11 +1123,11 @@ URL apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1139,7 +1143,7 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -1147,7 +1151,7 @@ Примітка apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -1155,7 +1159,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 275 + 271 @@ -1207,7 +1211,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 124 + 120 @@ -1223,7 +1227,7 @@ Ви дійсно хочете видалити цей купон? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 193 + 196 @@ -1231,7 +1235,7 @@ Ви дійсно хочете видалити це системне повідомлення? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 206 + 209 @@ -1239,7 +1243,7 @@ Ви дійсно хочете очистити кеш? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 230 + 233 @@ -1247,7 +1251,7 @@ Будь ласка, встановіть ваше системне повідомлення: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 250 + 253 @@ -1303,7 +1307,7 @@ Збір даних apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -1375,7 +1379,15 @@ Ви дійсно хочете видалити цю платформу? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 111 + + + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 @@ -1399,7 +1411,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 @@ -1423,7 +1435,7 @@ Дійсне до apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -1435,7 +1447,7 @@ з apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -1443,7 +1455,7 @@ щоденних запитів apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -1451,7 +1463,7 @@ Вилучити ключ API apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -1459,7 +1471,7 @@ Встановити ключ API apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -1467,7 +1479,7 @@ Платформи apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -1475,7 +1487,7 @@ Теги apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1491,7 +1503,7 @@ Ви дійсно хочете видалити ключ API? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -1499,7 +1511,7 @@ Будь ласка, введіть ваш ключ API Ghostfolio. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -1583,7 +1595,7 @@ Ви дійсно хочете видалити цей тег? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 108 @@ -1619,7 +1631,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 89 + 108 @@ -1631,7 +1643,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 98 + 117 @@ -1663,11 +1675,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -1715,7 +1727,7 @@ Порівняльний показник apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1791,15 +1803,15 @@ Упс! Неправильний Секретний Токен. apps/client/src/app/components/header/header.component.ts - 312 + 313 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 154 + 152 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 193 + 191 @@ -1851,7 +1863,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 189 + 185 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1879,7 +1891,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 88 + 92 @@ -1903,7 +1915,7 @@ Повідомити про збій даних apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 451 + 456 @@ -1911,7 +1923,7 @@ Активний apps/client/src/app/components/home-holdings/home-holdings.component.ts - 64 + 63 @@ -1919,7 +1931,7 @@ Закритий apps/client/src/app/components/home-holdings/home-holdings.component.ts - 65 + 64 @@ -1943,7 +1955,7 @@ Керування діяльністю apps/client/src/app/components/home-holdings/home-holdings.html - 67 + 64 @@ -1951,11 +1963,11 @@ Страх apps/client/src/app/components/home-market/home-market.component.ts - 42 + 41 apps/client/src/app/components/markets/markets.component.ts - 47 + 46 libs/ui/src/lib/i18n.ts @@ -1967,11 +1979,11 @@ Жадібність apps/client/src/app/components/home-market/home-market.component.ts - 43 + 42 apps/client/src/app/components/markets/markets.component.ts - 48 + 47 libs/ui/src/lib/i18n.ts @@ -2075,7 +2087,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 @@ -2095,7 +2107,7 @@ Загальна сума apps/client/src/app/components/investment-chart/investment-chart.component.ts - 143 + 146 @@ -2187,7 +2199,7 @@ Абсолютний валовий дохід apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 73 + 77 @@ -2195,7 +2207,7 @@ Абсолютний чистий прибуток apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 107 + 111 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -2207,7 +2219,7 @@ Чистий прибуток apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 123 + 127 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -2219,7 +2231,7 @@ Загальні активи apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 149 + 153 @@ -2227,7 +2239,7 @@ Активи apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 226 + 233 @@ -2235,7 +2247,7 @@ Купівельна спроможність apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 241 + 248 @@ -2243,7 +2255,7 @@ Виключено з аналізу apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 267 + 274 @@ -2251,7 +2263,7 @@ Зобов’язання apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 295 + 302 apps/client/src/app/pages/features/features-page.html @@ -2263,7 +2275,7 @@ Чиста вартість apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 317 + 324 @@ -2271,7 +2283,7 @@ Річна доходність apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 331 + 338 @@ -2279,7 +2291,7 @@ Зберегти apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -2315,11 +2327,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 356 + 352 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 48 + 49 @@ -2551,7 +2563,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 363 + 362 @@ -2559,11 +2571,11 @@ З початку року apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -2571,11 +2583,11 @@ 1 рік apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -2583,11 +2595,11 @@ 5 років apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -2603,11 +2615,11 @@ Максимум apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 217 libs/ui/src/lib/assistant/assistant.component.ts - 415 + 414 @@ -2663,7 +2675,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 11 + 30 @@ -2675,7 +2687,7 @@ apps/client/src/app/components/user-account-access/user-account-access.component.ts - 260 + 257 @@ -2723,7 +2735,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 196 + 195 @@ -2823,7 +2835,7 @@ Автоматичний apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 70 + 69 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2835,7 +2847,7 @@ Ви дійсно хочете закрити ваш обліковий запис Ghostfolio? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 205 @@ -2843,7 +2855,7 @@ Ви дійсно хочете вилучити цей спосіб входу? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 279 @@ -2851,7 +2863,7 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -2859,7 +2871,7 @@ Упс! Виникла помилка під час налаштування біометричної автентифікації. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 336 + 333 @@ -2907,7 +2919,7 @@ Локалізація apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -3051,7 +3063,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 195 + 194 @@ -3071,7 +3083,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 193 + 192 @@ -3207,11 +3219,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 375 + 381 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 66 + 85 apps/client/src/app/pages/accounts/accounts-page.html @@ -3311,7 +3323,7 @@ Ринкові дані apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 398 + 403 libs/common/src/lib/routes/routes.ts @@ -3345,6 +3357,10 @@ Overview Огляд + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 7 + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 114 @@ -3363,11 +3379,11 @@ apps/client/src/app/pages/admin/admin-page.component.ts - 48 + 45 apps/client/src/app/pages/resources/resources-page.component.ts - 30 + 29 libs/common/src/lib/routes/routes.ts @@ -3499,11 +3515,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -3519,7 +3535,7 @@ Оскільки ви вже ввійшли, ви не можете отримати доступ до демонстраційного обліковий запис. apps/client/src/app/pages/demo/demo-page.component.ts - 35 + 32 @@ -3547,7 +3563,7 @@ Загальні apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -3555,7 +3571,7 @@ Хмара apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -3567,7 +3583,7 @@ Самохостинг apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -3716,7 +3732,7 @@ apps/client/src/app/pages/public/public-page.html - 242 + 241 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -3768,7 +3784,7 @@ Ринки apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -3792,7 +3808,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 40 + 39 libs/common/src/lib/routes/routes.ts @@ -4240,7 +4256,7 @@ Job ID apps/client/src/app/components/admin-jobs/admin-jobs.html - 34 + 43 @@ -4276,7 +4292,7 @@ apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 115 + 113 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -4296,11 +4312,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 342 + 348 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 75 + 94 apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -4380,7 +4396,7 @@ Оновити баланс готівки apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 112 + 108 @@ -4388,7 +4404,7 @@ Дата apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 161 + 157 libs/ui/src/lib/account-balances/account-balances.component.html @@ -4408,7 +4424,7 @@ Ціна за одиницю apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 214 + 210 libs/ui/src/lib/activities-table/activities-table.component.html @@ -4436,7 +4452,7 @@ Імпорт дивідендів apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 137 + 136 libs/ui/src/lib/activities-table/activities-table.component.html @@ -4452,7 +4468,7 @@ Імпортуються дані... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 175 + 174 @@ -4460,7 +4476,7 @@ Імпорт завершено apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 185 + 184 @@ -4476,7 +4492,7 @@ Перевірка даних... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 299 + 293 @@ -4572,7 +4588,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 71 + 67 @@ -4664,7 +4680,7 @@ apps/client/src/app/pages/public/public-page.html - 151 + 150 @@ -4688,7 +4704,7 @@ apps/client/src/app/pages/public/public-page.html - 168 + 167 @@ -4696,7 +4712,7 @@ Latest activities apps/client/src/app/pages/public/public-page.html - 211 + 210 @@ -4708,7 +4724,7 @@ apps/client/src/app/pages/public/public-page.html - 177 + 176 @@ -4720,7 +4736,7 @@ apps/client/src/app/pages/public/public-page.html - 186 + 185 @@ -4796,7 +4812,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 365 + 372 apps/client/src/app/pages/features/features-page.html @@ -4804,11 +4820,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 202 + 198 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 76 + 75 libs/ui/src/lib/i18n.ts @@ -4828,11 +4844,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 81 + 80 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 97 + 96 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -4852,7 +4868,7 @@ Щомісячно apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 91 + 90 @@ -4860,7 +4876,7 @@ Щорічно apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 91 @@ -4868,7 +4884,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 447 @@ -5204,11 +5220,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -5235,6 +5251,14 @@ 42 + + has been copied to the clipboard + has been copied to the clipboard + + libs/ui/src/lib/value/value.component.ts + 180 + + From the beginning З початку @@ -5248,7 +5272,7 @@ Континенти apps/client/src/app/pages/public/public-page.html - 132 + 131 @@ -5256,7 +5280,7 @@ Чи хотіли б ви удосконалити вашу особисту інвестиційну стратегію? apps/client/src/app/pages/public/public-page.html - 234 + 233 @@ -5272,7 +5296,7 @@ Ghostfolio надає можливість вам стежити за вашим багатством. apps/client/src/app/pages/public/public-page.html - 238 + 237 @@ -5302,6 +5326,10 @@ apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 88 + + libs/ui/src/lib/value/value.component.html + 18 + Glossary @@ -5312,7 +5340,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 45 + 44 libs/common/src/lib/routes/routes.ts @@ -5328,7 +5356,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 34 + 33 libs/common/src/lib/routes/routes.ts @@ -6003,7 +6031,7 @@ Членство apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 48 + 67 libs/common/src/lib/routes/routes.ts @@ -6071,7 +6099,7 @@ Ви дійсно хочете видалити цей рахунок? libs/ui/src/lib/account-balances/account-balances.component.ts - 120 + 113 @@ -6135,7 +6163,7 @@ Ви дійсно хочете видалити ці дії? libs/ui/src/lib/activities-table/activities-table.component.ts - 278 + 282 @@ -6143,7 +6171,7 @@ Ви дійсно хочете видалити цю активність? libs/ui/src/lib/activities-table/activities-table.component.ts - 288 + 292 @@ -6151,7 +6179,7 @@ Тиждень до дати libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -6159,11 +6187,11 @@ WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -6171,7 +6199,7 @@ Місяць до дати libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -6179,11 +6207,11 @@ MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -6191,7 +6219,7 @@ Рік до дати libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -6199,7 +6227,7 @@ рік apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6211,7 +6239,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -6219,11 +6247,11 @@ роки apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -6231,7 +6259,7 @@ Профілі активів apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -6371,7 +6399,7 @@ Депозит libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 377 + 404 @@ -6383,11 +6411,11 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 352 + 359 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 387 + 414 libs/ui/src/lib/i18n.ts @@ -6399,7 +6427,7 @@ Заощадження libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 397 + 424 @@ -6415,7 +6443,7 @@ Упс! Не вдалося отримати історичні дані. libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts - 262 + 284 @@ -6443,7 +6471,7 @@ Показати все libs/ui/src/lib/holdings-table/holdings-table.component.html - 216 + 212 @@ -6487,7 +6515,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -6495,7 +6523,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 290 + 286 libs/ui/src/lib/i18n.ts @@ -6519,7 +6547,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -6527,7 +6555,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 309 + 305 libs/ui/src/lib/i18n.ts @@ -6551,7 +6579,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6583,7 +6611,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 345 + 341 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -6591,7 +6619,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 46 + 47 libs/ui/src/lib/i18n.ts @@ -6611,7 +6639,7 @@ Закрити apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6637,13 +6665,17 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 68 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 127 + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 107 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 347 + 343 libs/ui/src/lib/i18n.ts @@ -6679,7 +6711,7 @@ Резервний фонд apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 164 + 168 apps/client/src/app/pages/features/features-page.html @@ -6747,7 +6779,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 437 + 451 @@ -6766,6 +6798,14 @@ 27 + + No Activities + No Activities + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 145 + + Retirement Provision Пенсійне накопичення @@ -6795,7 +6835,7 @@ Символ apps/client/src/app/components/admin-jobs/admin-jobs.html - 68 + 77 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -6811,7 +6851,7 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 315 + 318 libs/ui/src/lib/i18n.ts @@ -6863,7 +6903,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 14 + 33 @@ -6899,7 +6939,7 @@ Комісія apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 262 + 258 libs/ui/src/lib/activities-table/activities-table.component.html @@ -6943,7 +6983,7 @@ Готівка apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 212 + 219 libs/ui/src/lib/i18n.ts @@ -6999,7 +7039,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 35 + 54 @@ -7339,7 +7379,7 @@ apps/client/src/app/pages/public/public-page.html - 196 + 195 libs/ui/src/lib/benchmark/benchmark.component.html @@ -7347,11 +7387,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 439 + 453 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 452 + 467 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -7387,7 +7427,7 @@ Запит AI скопійовано в буфер обміну apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 201 + 199 @@ -7395,7 +7435,7 @@ Lazy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7403,7 +7443,7 @@ Instant apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7411,7 +7451,7 @@ Default Market Price apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7419,7 +7459,7 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7427,7 +7467,7 @@ Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7435,7 +7475,7 @@ HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7443,7 +7483,7 @@ end of day apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7451,7 +7491,7 @@ real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7459,7 +7499,7 @@ Open Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 202 + 200 @@ -7479,7 +7519,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 @@ -7491,7 +7531,7 @@ apps/client/src/app/components/home-overview/home-overview.component.ts - 55 + 54 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -7499,11 +7539,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 381 + 390 @@ -7607,11 +7647,11 @@ Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 235 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 170 + 167 @@ -7619,7 +7659,7 @@ Do you really want to generate a new security token for this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 241 + 240 @@ -7627,7 +7667,7 @@ Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 151 + 115 @@ -7692,7 +7732,7 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7700,7 +7740,7 @@ An error occurred while updating to (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7764,7 +7804,7 @@ someone apps/client/src/app/pages/public/public-page.component.ts - 59 + 61 @@ -7796,7 +7836,7 @@ Do you really want to delete this item? libs/ui/src/lib/benchmark/benchmark.component.ts - 144 + 139 @@ -7837,7 +7877,7 @@ Demo user account has been synced. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 274 + 277 @@ -7872,7 +7912,7 @@ 150 - + Fee Ratio Fee Ratio @@ -7880,17 +7920,17 @@ 152 - - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) + + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 - - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) + + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -8051,7 +8091,7 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 @@ -8059,11 +8099,11 @@ new apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts - 56 + 53 @@ -8220,7 +8260,7 @@ Do you really want to generate a new security token? apps/client/src/app/components/user-account-access/user-account-access.component.ts - 175 + 172 @@ -8244,7 +8284,7 @@ Stocks apps/client/src/app/components/markets/markets.component.ts - 52 + 51 apps/client/src/app/pages/features/features-page.html @@ -8256,7 +8296,7 @@ Cryptocurrencies apps/client/src/app/components/markets/markets.component.ts - 53 + 52 apps/client/src/app/pages/features/features-page.html @@ -8276,7 +8316,7 @@ Manage Asset Profile apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 466 + 471 @@ -8696,7 +8736,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 26 + 45 diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index a6907698d..fa9e6b874 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -235,7 +235,7 @@ Type apps/client/src/app/components/admin-jobs/admin-jobs.html - 48 + 57 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -312,7 +312,7 @@ Cash Balances apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 148 + 146 @@ -334,7 +334,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -346,7 +346,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -362,7 +362,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 139 + 135 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -404,7 +404,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -416,7 +416,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 145 + 141 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -435,15 +435,15 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 205 + 201 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 208 + 204 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 211 + 207 libs/ui/src/lib/account-balances/account-balances.component.html @@ -490,11 +490,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -521,11 +521,15 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 libs/ui/src/lib/account-balances/account-balances.component.html @@ -548,32 +552,32 @@ Do you really want to delete this account? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 150 + 148 Asset Profile apps/client/src/app/components/admin-jobs/admin-jobs.html - 52 + 61 Historical Market Data apps/client/src/app/components/admin-jobs/admin-jobs.html - 54 + 63 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 Data Source apps/client/src/app/components/admin-jobs/admin-jobs.html - 82 + 91 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -585,7 +589,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 155 + 151 libs/ui/src/lib/i18n.ts @@ -596,32 +600,32 @@ Attempts apps/client/src/app/components/admin-jobs/admin-jobs.html - 120 + 129 Created apps/client/src/app/components/admin-jobs/admin-jobs.html - 134 + 143 Finished apps/client/src/app/components/admin-jobs/admin-jobs.html - 143 + 152 Status apps/client/src/app/components/admin-jobs/admin-jobs.html - 152 + 161 apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -635,28 +639,28 @@ Delete Jobs apps/client/src/app/components/admin-jobs/admin-jobs.html - 193 + 202 View Data apps/client/src/app/components/admin-jobs/admin-jobs.html - 208 + 217 View Stacktrace apps/client/src/app/components/admin-jobs/admin-jobs.html - 216 + 225 Delete Job apps/client/src/app/components/admin-jobs/admin-jobs.html - 224 + 233 @@ -670,7 +674,7 @@ Date apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 161 + 157 libs/ui/src/lib/account-balances/account-balances.component.html @@ -704,25 +708,25 @@ Currencies apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 131 + 130 apps/client/src/app/pages/public/public-page.html - 96 + 95 ETFs without Countries apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 136 + 135 ETFs without Sectors apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 141 + 140 @@ -736,7 +740,7 @@ Filter by... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 385 + 383 @@ -773,7 +777,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 44 + 40 @@ -819,7 +823,7 @@ Oops! Could not parse historical data. libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts - 262 + 284 @@ -848,7 +852,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 71 + 67 @@ -878,7 +882,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 53 + 72 @@ -889,7 +893,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -897,7 +901,7 @@ apps/client/src/app/pages/public/public-page.html - 114 + 113 @@ -908,7 +912,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -919,7 +923,7 @@ Symbol Mapping apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -933,14 +937,14 @@ Scraper Configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 Note apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -948,7 +952,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 275 + 271 @@ -988,35 +992,35 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 124 + 120 Do you really want to delete this coupon? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 193 + 196 Do you really want to delete this system message? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 206 + 209 Do you really want to flush the cache? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 230 + 233 Please set your system message: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 250 + 253 @@ -1125,11 +1129,11 @@ Url apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1144,14 +1148,21 @@ Asset profile has been saved apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 Do you really want to delete this platform? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 111 + + + + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 @@ -1172,7 +1183,7 @@ Current year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 @@ -1186,14 +1197,14 @@ Platforms apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 Tags apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1215,7 +1226,7 @@ Do you really want to delete this tag? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 108 @@ -1269,7 +1280,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 89 + 108 @@ -1297,11 +1308,11 @@ Could not validate form apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -1345,7 +1356,7 @@ Benchmark apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1378,7 +1389,7 @@ apps/client/src/app/components/header/header.component.ts - 297 + 298 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -1397,33 +1408,33 @@ Oops! Incorrect Security Token. apps/client/src/app/components/header/header.component.ts - 312 + 313 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 154 + 152 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 193 + 191 Manage Activities apps/client/src/app/components/home-holdings/home-holdings.html - 67 + 64 Fear apps/client/src/app/components/home-market/home-market.component.ts - 42 + 41 apps/client/src/app/components/markets/markets.component.ts - 47 + 46 libs/ui/src/lib/i18n.ts @@ -1434,11 +1445,11 @@ Greed apps/client/src/app/components/home-market/home-market.component.ts - 43 + 42 apps/client/src/app/components/markets/markets.component.ts - 48 + 47 libs/ui/src/lib/i18n.ts @@ -1530,7 +1541,7 @@ Current week apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 @@ -1548,7 +1559,7 @@ Total Amount apps/client/src/app/components/investment-chart/investment-chart.component.ts - 143 + 146 @@ -1653,7 +1664,7 @@ Absolute Gross Performance apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 73 + 77 @@ -1664,14 +1675,14 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 88 + 92 Absolute Net Performance apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 107 + 111 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -1682,7 +1693,7 @@ Net Performance apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 123 + 127 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -1693,35 +1704,35 @@ Total Assets apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 149 + 153 Assets apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 226 + 233 Buying Power apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 241 + 248 Excluded from Analysis apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 267 + 274 Liabilities apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 295 + 302 apps/client/src/app/pages/features/features-page.html @@ -1732,14 +1743,14 @@ Net Worth apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 317 + 324 Annualized Performance apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 331 + 338 @@ -1771,7 +1782,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 189 + 185 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1786,7 +1797,7 @@ Report Data Glitch apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 451 + 456 @@ -1954,40 +1965,40 @@ libs/ui/src/lib/assistant/assistant.component.ts - 363 + 362 YTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 1Y apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 5Y apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -2001,11 +2012,11 @@ Max apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 217 libs/ui/src/lib/assistant/assistant.component.ts - 415 + 414 @@ -2094,7 +2105,7 @@ Auto apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 70 + 69 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2105,7 +2116,7 @@ Do you really want to remove this sign in method? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 279 @@ -2140,7 +2151,7 @@ Locale apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2240,7 +2251,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 11 + 30 @@ -2269,7 +2280,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 195 + 194 @@ -2280,7 +2291,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 193 + 192 @@ -2295,7 +2306,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 196 + 195 @@ -2414,11 +2425,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 375 + 381 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 66 + 85 apps/client/src/app/pages/accounts/accounts-page.html @@ -2501,7 +2512,7 @@ Market Data apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 398 + 403 libs/common/src/lib/routes/routes.ts @@ -2532,6 +2543,10 @@ Overview + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 7 + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 114 @@ -2550,11 +2565,11 @@ apps/client/src/app/pages/admin/admin-page.component.ts - 48 + 45 apps/client/src/app/pages/resources/resources-page.component.ts - 30 + 29 libs/common/src/lib/routes/routes.ts @@ -2676,11 +2691,11 @@ Could not parse scraper configuration apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -2694,7 +2709,7 @@ As you are already logged in, you cannot access the demo account. apps/client/src/app/pages/demo/demo-page.component.ts - 35 + 32 @@ -2850,7 +2865,7 @@ apps/client/src/app/pages/public/public-page.html - 242 + 241 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -2899,7 +2914,7 @@ Markets apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -2923,7 +2938,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 40 + 39 libs/common/src/lib/routes/routes.ts @@ -3312,7 +3327,7 @@ Job ID apps/client/src/app/components/admin-jobs/admin-jobs.html - 34 + 43 @@ -3344,7 +3359,7 @@ apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 115 + 113 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -3364,11 +3379,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 342 + 348 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 75 + 94 apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -3387,7 +3402,7 @@ Do you really want to delete these activities? libs/ui/src/lib/activities-table/activities-table.component.ts - 278 + 282 @@ -3447,14 +3462,14 @@ Update Cash Balance apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 112 + 108 Unit Price apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 214 + 210 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3480,7 +3495,7 @@ Import Dividends apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 137 + 136 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3495,14 +3510,14 @@ Importing data... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 175 + 174 Import has been completed apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 185 + 184 @@ -3516,7 +3531,7 @@ Validating data... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 299 + 293 @@ -3669,7 +3684,7 @@ apps/client/src/app/pages/public/public-page.html - 151 + 150 @@ -3691,14 +3706,14 @@ apps/client/src/app/pages/public/public-page.html - 168 + 167 Latest activities apps/client/src/app/pages/public/public-page.html - 211 + 210 @@ -3709,7 +3724,7 @@ apps/client/src/app/pages/public/public-page.html - 177 + 176 @@ -3720,7 +3735,7 @@ apps/client/src/app/pages/public/public-page.html - 186 + 185 @@ -3774,7 +3789,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 365 + 372 apps/client/src/app/pages/features/features-page.html @@ -3782,11 +3797,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 202 + 198 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 76 + 75 libs/ui/src/lib/i18n.ts @@ -3804,21 +3819,21 @@ Deposit libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 377 + 404 Monthly apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 91 + 90 Yearly apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 91 @@ -4070,11 +4085,11 @@ Could not save asset profile apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -4095,7 +4110,7 @@ Continents apps/client/src/app/pages/public/public-page.html - 132 + 131 @@ -4109,7 +4124,7 @@ Ghostfolio empowers you to keep track of your wealth. apps/client/src/app/pages/public/public-page.html - 238 + 237 @@ -4136,6 +4151,10 @@ apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 88 + + libs/ui/src/lib/value/value.component.html + 18 + Personal Finance Tools @@ -4448,7 +4467,7 @@ Membership apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 48 + 67 libs/common/src/lib/routes/routes.ts @@ -4509,7 +4528,7 @@ Do you really want to delete this account balance? libs/ui/src/lib/account-balances/account-balances.component.ts - 120 + 113 @@ -4559,14 +4578,14 @@ Do you really want to delete this activity? libs/ui/src/lib/activities-table/activities-table.component.ts - 288 + 292 Asset Profiles apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -4665,11 +4684,11 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 352 + 359 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 387 + 414 libs/ui/src/lib/i18n.ts @@ -4680,7 +4699,7 @@ Savings libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 397 + 424 @@ -4706,7 +4725,7 @@ Show all libs/ui/src/lib/holdings-table/holdings-table.component.html - 216 + 212 @@ -4747,7 +4766,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -4755,7 +4774,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 290 + 286 libs/ui/src/lib/i18n.ts @@ -4778,7 +4797,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -4786,7 +4805,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 309 + 305 libs/ui/src/lib/i18n.ts @@ -4825,7 +4844,7 @@ Emergency Fund apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 164 + 168 apps/client/src/app/pages/features/features-page.html @@ -4893,7 +4912,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 437 + 451 @@ -4903,6 +4922,13 @@ 27 + + No Activities + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 145 + + Retirement Provision @@ -4921,7 +4947,7 @@ Symbol apps/client/src/app/components/admin-jobs/admin-jobs.html - 68 + 77 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -4937,7 +4963,7 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 315 + 318 libs/ui/src/lib/i18n.ts @@ -5002,7 +5028,7 @@ Fee apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 262 + 258 libs/ui/src/lib/activities-table/activities-table.component.html @@ -5042,7 +5068,7 @@ Cash apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 212 + 219 libs/ui/src/lib/i18n.ts @@ -5085,7 +5111,7 @@ Authentication apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 35 + 54 @@ -5211,7 +5237,7 @@ Valid until apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -5233,7 +5259,7 @@ apps/client/src/app/pages/public/public-page.html - 196 + 195 libs/ui/src/lib/benchmark/benchmark.component.html @@ -5241,11 +5267,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 439 + 453 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 452 + 467 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -5270,14 +5296,14 @@ The current market price is apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 Test apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5348,7 +5374,7 @@ Close Holding apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 447 @@ -5370,11 +5396,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 81 + 80 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 97 + 96 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5406,43 +5432,43 @@ Year to date libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 Week to date libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 Month to date libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 MTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 WTD apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5474,7 +5500,7 @@ year apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -5486,18 +5512,18 @@ libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 years apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -5523,7 +5549,7 @@ Self-Hosting apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -5534,7 +5560,7 @@ Data Gathering apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -5545,14 +5571,14 @@ General apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 Cloud apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -5577,14 +5603,14 @@ Closed apps/client/src/app/components/home-holdings/home-holdings.component.ts - 65 + 64 Active apps/client/src/app/components/home-holdings/home-holdings.component.ts - 64 + 63 @@ -5612,7 +5638,7 @@ Execute Job apps/client/src/app/components/admin-jobs/admin-jobs.html - 220 + 229 @@ -5626,7 +5652,7 @@ Priority apps/client/src/app/components/admin-jobs/admin-jobs.html - 96 + 105 @@ -5675,7 +5701,7 @@ Do you really want to close your Ghostfolio account? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 205 @@ -5710,14 +5736,14 @@ Include in apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 Oops! There was an error setting up biometric authentication. apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 336 + 333 @@ -5752,7 +5778,7 @@ Benchmarks apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 126 + 125 @@ -5773,7 +5799,7 @@ Would you like to refine your personal investment strategy? apps/client/src/app/pages/public/public-page.html - 234 + 233 @@ -6071,7 +6097,7 @@ Error apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6082,7 +6108,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6114,7 +6140,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 345 + 341 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -6122,7 +6148,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 46 + 47 libs/ui/src/lib/i18n.ts @@ -6133,7 +6159,7 @@ Role apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 14 + 33 @@ -6161,7 +6187,7 @@ Close apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6187,13 +6213,17 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 68 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 127 + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 107 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 347 + 343 libs/ui/src/lib/i18n.ts @@ -6246,7 +6276,7 @@ Portfolio Snapshot apps/client/src/app/components/admin-jobs/admin-jobs.html - 56 + 65 @@ -6298,6 +6328,13 @@ 42 + + has been copied to the clipboard + + libs/ui/src/lib/value/value.component.ts + 180 + + offers a free plan @@ -6439,7 +6476,7 @@ Set API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -6471,7 +6508,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 45 + 44 libs/common/src/lib/routes/routes.ts @@ -6498,7 +6535,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 34 + 33 libs/common/src/lib/routes/routes.ts @@ -6542,28 +6579,28 @@ of apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 Do you really want to delete the API key? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 Remove API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 daily requests apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -6588,7 +6625,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 98 + 117 @@ -6637,7 +6674,7 @@ Save apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6673,11 +6710,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 356 + 352 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 48 + 49 @@ -6702,14 +6739,14 @@ apps/client/src/app/components/user-account-access/user-account-access.component.ts - 260 + 257 Please enter your Ghostfolio API key. apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -6723,7 +6760,7 @@ AI prompt has been copied to the clipboard apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 201 + 199 @@ -6737,63 +6774,63 @@ Mode apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 Default Market Price apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 Selector apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 Instant apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 Lazy apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 HTTP Request Headers apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 real-time apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 end of day apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 Open Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 202 + 200 @@ -6811,7 +6848,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 @@ -6822,7 +6859,7 @@ apps/client/src/app/components/home-overview/home-overview.component.ts - 55 + 54 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -6830,11 +6867,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 381 + 390 @@ -6925,25 +6962,25 @@ Do you really want to generate a new security token for this user? apps/client/src/app/components/admin-users/admin-users.component.ts - 241 + 240 Find account, holding or page... libs/ui/src/lib/assistant/assistant.component.ts - 151 + 115 Security token apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 235 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 170 + 167 @@ -7001,14 +7038,14 @@ () is already in use. apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 An error occurred while updating to (). apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7043,7 +7080,7 @@ someone apps/client/src/app/pages/public/public-page.component.ts - 59 + 61 @@ -7072,7 +7109,7 @@ Do you really want to delete this item? libs/ui/src/lib/benchmark/benchmark.component.ts - 144 + 139 @@ -7116,7 +7153,7 @@ Demo user account has been synced. apps/client/src/app/components/admin-overview/admin-overview.component.ts - 274 + 277 @@ -7140,22 +7177,22 @@ 150 - + Fee Ratio apps/client/src/app/pages/i18n/i18n-page.html 152 - - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) + + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 - - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) + + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -7300,18 +7337,18 @@ Current month apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 new apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts - 56 + 53 @@ -7463,14 +7500,14 @@ Do you really want to generate a new security token? apps/client/src/app/components/user-account-access/user-account-access.component.ts - 175 + 172 Cryptocurrencies apps/client/src/app/components/markets/markets.component.ts - 53 + 52 apps/client/src/app/pages/features/features-page.html @@ -7481,7 +7518,7 @@ Stocks apps/client/src/app/components/markets/markets.component.ts - 52 + 51 apps/client/src/app/pages/features/features-page.html @@ -7499,7 +7536,7 @@ Manage Asset Profile apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 466 + 471 @@ -7868,7 +7905,7 @@ Registration Date apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 26 + 45 diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 7ec9d85a0..78c9b717d 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -252,7 +252,7 @@ 类型 apps/client/src/app/components/admin-jobs/admin-jobs.html - 48 + 57 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -336,7 +336,7 @@ 现金余额 apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 148 + 146 @@ -360,7 +360,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 309 + 310 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -372,7 +372,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 46 + 58 apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -388,7 +388,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 139 + 135 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -432,7 +432,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 316 + 317 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -444,7 +444,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 145 + 141 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -464,15 +464,15 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 205 + 201 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 208 + 204 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 211 + 207 libs/ui/src/lib/account-balances/account-balances.component.html @@ -520,11 +520,11 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 74 + 78 apps/client/src/app/components/admin-tag/admin-tag.component.html - 67 + 71 libs/ui/src/lib/accounts-table/accounts-table.component.html @@ -552,11 +552,15 @@ apps/client/src/app/components/admin-platform/admin-platform.component.html - 85 + 89 apps/client/src/app/components/admin-tag/admin-tag.component.html - 78 + 82 + + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 22 libs/ui/src/lib/account-balances/account-balances.component.html @@ -580,7 +584,7 @@ 您确定要删除此账户吗? libs/ui/src/lib/accounts-table/accounts-table.component.ts - 150 + 148 @@ -588,7 +592,7 @@ 资产概况 apps/client/src/app/components/admin-jobs/admin-jobs.html - 52 + 61 @@ -596,11 +600,11 @@ 历史市场数据 apps/client/src/app/components/admin-jobs/admin-jobs.html - 54 + 63 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 448 + 451 @@ -608,7 +612,7 @@ 数据源 apps/client/src/app/components/admin-jobs/admin-jobs.html - 82 + 91 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -620,7 +624,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 155 + 151 libs/ui/src/lib/i18n.ts @@ -632,7 +636,7 @@ 尝试次数 apps/client/src/app/components/admin-jobs/admin-jobs.html - 120 + 129 @@ -640,7 +644,7 @@ 创建 apps/client/src/app/components/admin-jobs/admin-jobs.html - 134 + 143 @@ -648,7 +652,7 @@ 完成 apps/client/src/app/components/admin-jobs/admin-jobs.html - 143 + 152 @@ -656,11 +660,11 @@ 状态 apps/client/src/app/components/admin-jobs/admin-jobs.html - 152 + 161 apps/client/src/app/components/admin-settings/admin-settings.component.html - 92 + 104 @@ -676,7 +680,7 @@ 删除任务 apps/client/src/app/components/admin-jobs/admin-jobs.html - 193 + 202 @@ -684,7 +688,7 @@ 查看数据 apps/client/src/app/components/admin-jobs/admin-jobs.html - 208 + 217 @@ -692,7 +696,7 @@ 查看堆栈跟踪 apps/client/src/app/components/admin-jobs/admin-jobs.html - 216 + 225 @@ -700,7 +704,7 @@ 删除任务 apps/client/src/app/components/admin-jobs/admin-jobs.html - 224 + 233 @@ -716,7 +720,7 @@ 日期 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 161 + 157 libs/ui/src/lib/account-balances/account-balances.component.html @@ -752,11 +756,11 @@ 货币 apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 131 + 130 apps/client/src/app/pages/public/public-page.html - 96 + 95 @@ -764,7 +768,7 @@ 没有国家的 ETF apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 136 + 135 @@ -772,7 +776,7 @@ 无行业类别的 ETF apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 141 + 140 @@ -788,7 +792,7 @@ 过滤... apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 385 + 383 @@ -828,7 +832,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 44 + 40 @@ -864,7 +868,7 @@ 哎呀!无法解析历史数据。 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts - 262 + 284 @@ -888,7 +892,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html - 71 + 67 @@ -920,7 +924,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 53 + 72 @@ -932,7 +936,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 396 + 399 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -940,7 +944,7 @@ apps/client/src/app/pages/public/public-page.html - 114 + 113 @@ -952,7 +956,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 407 + 410 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -964,7 +968,7 @@ 代码映射 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 384 + 387 @@ -980,7 +984,7 @@ 刮削配置 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 471 + 474 @@ -988,7 +992,7 @@ 笔记 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 432 + 435 apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -996,7 +1000,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 275 + 271 @@ -1040,7 +1044,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 124 + 120 @@ -1048,7 +1052,7 @@ 您确实要删除此优惠券吗? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 193 + 196 @@ -1056,7 +1060,7 @@ 您真的要删除这条系统消息吗? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 206 + 209 @@ -1064,7 +1068,7 @@ 您真的要刷新缓存吗? apps/client/src/app/components/admin-overview/admin-overview.component.ts - 230 + 233 @@ -1072,7 +1076,7 @@ 请设置您的系统消息: apps/client/src/app/components/admin-overview/admin-overview.component.ts - 250 + 253 @@ -1196,11 +1200,11 @@ 网址 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 419 + 422 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 550 + 553 apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -1216,7 +1220,7 @@ 资产概况已保存 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 578 + 594 @@ -1224,7 +1228,15 @@ 您真的要删除这个平台吗? apps/client/src/app/components/admin-platform/admin-platform.component.ts - 106 + 111 + + + + Explore + Explore + + apps/client/src/app/pages/resources/overview/resources-overview.component.html + 11 @@ -1248,7 +1260,7 @@ 当前年份 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 @@ -1264,7 +1276,7 @@ 平台 apps/client/src/app/components/admin-settings/admin-settings.component.html - 195 + 212 @@ -1272,7 +1284,7 @@ 标签 apps/client/src/app/components/admin-settings/admin-settings.component.html - 201 + 218 libs/ui/src/lib/tags-selector/tags-selector.component.html @@ -1296,7 +1308,7 @@ 您真的要删除此标签吗? apps/client/src/app/components/admin-tag/admin-tag.component.ts - 103 + 108 @@ -1356,7 +1368,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 89 + 108 @@ -1388,11 +1400,11 @@ 无法验证表单 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 554 + 570 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 557 + 573 @@ -1440,7 +1452,7 @@ 基准 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 376 + 379 apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts @@ -1476,7 +1488,7 @@ apps/client/src/app/components/header/header.component.ts - 297 + 298 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -1496,15 +1508,15 @@ 哎呀!安全令牌不正确。 apps/client/src/app/components/header/header.component.ts - 312 + 313 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 154 + 152 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 193 + 191 @@ -1512,7 +1524,7 @@ 管理活动 apps/client/src/app/components/home-holdings/home-holdings.html - 67 + 64 @@ -1520,11 +1532,11 @@ 恐惧 apps/client/src/app/components/home-market/home-market.component.ts - 42 + 41 apps/client/src/app/components/markets/markets.component.ts - 47 + 46 libs/ui/src/lib/i18n.ts @@ -1536,11 +1548,11 @@ 贪婪 apps/client/src/app/components/home-market/home-market.component.ts - 43 + 42 apps/client/src/app/components/markets/markets.component.ts - 48 + 47 libs/ui/src/lib/i18n.ts @@ -1644,7 +1656,7 @@ 当前周 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 @@ -1664,7 +1676,7 @@ 总金额 apps/client/src/app/components/investment-chart/investment-chart.component.ts - 143 + 146 @@ -1776,7 +1788,7 @@ 绝对总业绩 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 73 + 77 @@ -1788,7 +1800,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 88 + 92 @@ -1796,7 +1808,7 @@ 绝对净绩效 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 107 + 111 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -1808,7 +1820,7 @@ 净绩效 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 123 + 127 apps/client/src/app/pages/portfolio/analysis/analysis-page.html @@ -1820,7 +1832,7 @@ 总资产 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 149 + 153 @@ -1828,7 +1840,7 @@ 资产 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 226 + 233 @@ -1836,7 +1848,7 @@ 购买力 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 241 + 248 @@ -1844,7 +1856,7 @@ 从分析中排除 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 267 + 274 @@ -1852,7 +1864,7 @@ 负债 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 295 + 302 apps/client/src/app/pages/features/features-page.html @@ -1864,7 +1876,7 @@ 净值 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 317 + 324 @@ -1872,7 +1884,7 @@ 年化业绩 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 331 + 338 @@ -1908,7 +1920,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 189 + 185 libs/ui/src/lib/activities-table/activities-table.component.html @@ -1924,7 +1936,7 @@ 报告数据故障 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 451 + 456 @@ -2104,7 +2116,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 363 + 362 @@ -2112,11 +2124,11 @@ 年初至今 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 199 + 205 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -2124,11 +2136,11 @@ 1年 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -2136,11 +2148,11 @@ 5年 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -2156,11 +2168,11 @@ 最大限度 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 211 + 217 libs/ui/src/lib/assistant/assistant.component.ts - 415 + 414 @@ -2260,7 +2272,7 @@ 自动 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 70 + 69 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2272,7 +2284,7 @@ 您确实要删除此登录方法吗? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 282 + 279 @@ -2312,7 +2324,7 @@ 语言环境 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 509 + 512 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2424,7 +2436,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 11 + 30 @@ -2456,7 +2468,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 195 + 194 @@ -2468,7 +2480,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 193 + 192 @@ -2484,7 +2496,7 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 196 + 195 @@ -2612,11 +2624,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 375 + 381 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 66 + 85 apps/client/src/app/pages/accounts/accounts-page.html @@ -2708,7 +2720,7 @@ 市场数据 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 398 + 403 libs/common/src/lib/routes/routes.ts @@ -2742,6 +2754,10 @@ Overview 概述 + + apps/client/src/app/components/admin-jobs/admin-jobs.html + 7 + apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html 114 @@ -2760,11 +2776,11 @@ apps/client/src/app/pages/admin/admin-page.component.ts - 48 + 45 apps/client/src/app/pages/resources/resources-page.component.ts - 30 + 29 libs/common/src/lib/routes/routes.ts @@ -2888,11 +2904,11 @@ 无法解析抓取器配置 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 509 + 525 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 512 + 528 @@ -2908,7 +2924,7 @@ 由于您已经登录,因此无法访问模拟帐户。 apps/client/src/app/pages/demo/demo-page.component.ts - 35 + 32 @@ -3080,7 +3096,7 @@ apps/client/src/app/pages/public/public-page.html - 242 + 241 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -3132,7 +3148,7 @@ 市场 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 378 + 381 apps/client/src/app/components/footer/footer.component.html @@ -3156,7 +3172,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 40 + 39 libs/common/src/lib/routes/routes.ts @@ -3596,7 +3612,7 @@ 作业 ID apps/client/src/app/components/admin-jobs/admin-jobs.html - 34 + 43 @@ -3632,7 +3648,7 @@ apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html - 115 + 113 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -3652,11 +3668,11 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 342 + 348 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 75 + 94 apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -3676,7 +3692,7 @@ 您确定要删除这些活动吗? libs/ui/src/lib/activities-table/activities-table.component.ts - 278 + 282 @@ -3744,7 +3760,7 @@ 更新现金余额 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 112 + 108 @@ -3752,7 +3768,7 @@ 单价 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 214 + 210 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3780,7 +3796,7 @@ 导入股息 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 137 + 136 libs/ui/src/lib/activities-table/activities-table.component.html @@ -3796,7 +3812,7 @@ 正在导入数据... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 175 + 174 @@ -3804,7 +3820,7 @@ 导入已完成 apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 185 + 184 @@ -3820,7 +3836,7 @@ 验证数据... apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts - 299 + 293 @@ -3992,7 +4008,7 @@ apps/client/src/app/pages/public/public-page.html - 151 + 150 @@ -4016,7 +4032,7 @@ apps/client/src/app/pages/public/public-page.html - 168 + 167 @@ -4024,7 +4040,7 @@ 最新活动 apps/client/src/app/pages/public/public-page.html - 211 + 210 @@ -4036,7 +4052,7 @@ apps/client/src/app/pages/public/public-page.html - 177 + 176 @@ -4048,7 +4064,7 @@ apps/client/src/app/pages/public/public-page.html - 186 + 185 @@ -4108,7 +4124,7 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 365 + 372 apps/client/src/app/pages/features/features-page.html @@ -4116,11 +4132,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 202 + 198 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 76 + 75 libs/ui/src/lib/i18n.ts @@ -4140,7 +4156,7 @@ 存款 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 377 + 404 @@ -4148,7 +4164,7 @@ 每月 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 91 + 90 @@ -4156,7 +4172,7 @@ 每年 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 92 + 91 @@ -4436,11 +4452,11 @@ 无法保存资产概况 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 588 + 604 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 591 + 607 @@ -4464,7 +4480,7 @@ 大陆 apps/client/src/app/pages/public/public-page.html - 132 + 131 @@ -4480,7 +4496,7 @@ Ghostfolio 使您能够跟踪您的财富。 apps/client/src/app/pages/public/public-page.html - 238 + 237 @@ -4510,6 +4526,10 @@ apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html 88 + + libs/ui/src/lib/value/value.component.html + 18 + Personal Finance Tools @@ -4853,7 +4873,7 @@ 会员资格 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 48 + 67 libs/common/src/lib/routes/routes.ts @@ -4921,7 +4941,7 @@ 您确实要删除该帐户余额吗? libs/ui/src/lib/account-balances/account-balances.component.ts - 120 + 113 @@ -4977,7 +4997,7 @@ 您确实要删除此活动吗? libs/ui/src/lib/activities-table/activities-table.component.ts - 288 + 292 @@ -4985,7 +5005,7 @@ 资产概况 apps/client/src/app/components/admin-settings/admin-settings.component.html - 106 + 123 libs/ui/src/lib/assistant/assistant.html @@ -5097,11 +5117,11 @@ apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 352 + 359 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 387 + 414 libs/ui/src/lib/i18n.ts @@ -5113,7 +5133,7 @@ 储蓄 libs/ui/src/lib/fire-calculator/fire-calculator.component.ts - 397 + 424 @@ -5141,7 +5161,7 @@ 显示所有 libs/ui/src/lib/holdings-table/holdings-table.component.html - 216 + 212 @@ -5185,7 +5205,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 326 + 327 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5193,7 +5213,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 290 + 286 libs/ui/src/lib/i18n.ts @@ -5217,7 +5237,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 342 + 343 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -5225,7 +5245,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 309 + 305 libs/ui/src/lib/i18n.ts @@ -5269,7 +5289,7 @@ 应急基金 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 164 + 168 apps/client/src/app/pages/features/features-page.html @@ -5345,7 +5365,7 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 437 + 451 @@ -5356,6 +5376,14 @@ 27 + + No Activities + No Activities + + apps/client/src/app/components/admin-market-data/admin-market-data.component.ts + 145 + + Retirement Provision 退休金 @@ -5377,7 +5405,7 @@ 代码 apps/client/src/app/components/admin-jobs/admin-jobs.html - 68 + 77 apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -5393,7 +5421,7 @@ apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 315 + 318 libs/ui/src/lib/i18n.ts @@ -5465,7 +5493,7 @@ 费用 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 262 + 258 libs/ui/src/lib/activities-table/activities-table.component.html @@ -5509,7 +5537,7 @@ 现金 apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html - 212 + 219 libs/ui/src/lib/i18n.ts @@ -5557,7 +5585,7 @@ 认证 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 35 + 54 @@ -5701,7 +5729,7 @@ 有效期至 apps/client/src/app/components/admin-settings/admin-settings.component.html - 74 + 86 libs/ui/src/lib/membership-card/membership-card.component.html @@ -5725,7 +5753,7 @@ apps/client/src/app/pages/public/public-page.html - 196 + 195 libs/ui/src/lib/benchmark/benchmark.component.html @@ -5733,11 +5761,11 @@ libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 439 + 453 libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts - 452 + 467 libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -5765,7 +5793,7 @@ 当前市场价格为 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 706 + 722 @@ -5773,7 +5801,7 @@ 测试 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 568 + 571 @@ -5853,7 +5881,7 @@ 关闭持仓 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 442 + 447 @@ -5877,11 +5905,11 @@ apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 81 + 80 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 97 + 96 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5917,7 +5945,7 @@ 今年迄今为止 libs/ui/src/lib/assistant/assistant.component.ts - 375 + 374 @@ -5925,7 +5953,7 @@ 本周至今 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5933,7 +5961,7 @@ 本月至今 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5941,11 +5969,11 @@ 本月至今 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 libs/ui/src/lib/assistant/assistant.component.ts - 371 + 370 @@ -5953,11 +5981,11 @@ 本周至今 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 191 + 197 libs/ui/src/lib/assistant/assistant.component.ts - 367 + 366 @@ -5993,7 +6021,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 203 + 209 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -6005,7 +6033,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 385 + 384 @@ -6013,11 +6041,11 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 207 + 213 libs/ui/src/lib/assistant/assistant.component.ts - 409 + 408 @@ -6046,7 +6074,7 @@ 自托管 apps/client/src/app/pages/faq/faq-page.component.ts - 60 + 52 libs/common/src/lib/routes/routes.ts @@ -6058,7 +6086,7 @@ 数据收集 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 587 + 594 apps/client/src/app/components/admin-overview/admin-overview.html @@ -6070,7 +6098,7 @@ 一般的 apps/client/src/app/pages/faq/faq-page.component.ts - 49 + 41 @@ -6078,7 +6106,7 @@ apps/client/src/app/pages/faq/faq-page.component.ts - 54 + 46 libs/common/src/lib/routes/routes.ts @@ -6106,7 +6134,7 @@ 已关闭 apps/client/src/app/components/home-holdings/home-holdings.component.ts - 65 + 64 @@ -6114,7 +6142,7 @@ 活跃 apps/client/src/app/components/home-holdings/home-holdings.component.ts - 64 + 63 @@ -6146,7 +6174,7 @@ 执行作业 apps/client/src/app/components/admin-jobs/admin-jobs.html - 220 + 229 @@ -6154,7 +6182,7 @@ 优先级 apps/client/src/app/components/admin-jobs/admin-jobs.html - 96 + 105 @@ -6210,7 +6238,7 @@ 您确定要关闭您的 Ghostfolio 账户吗? apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 208 + 205 @@ -6258,7 +6286,7 @@ 包含在 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 374 + 377 @@ -6266,7 +6294,7 @@ 哎呀!设置生物识别认证时发生错误。 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts - 336 + 333 @@ -6282,7 +6310,7 @@ 基准 apps/client/src/app/components/admin-market-data/admin-market-data.component.ts - 126 + 125 @@ -6330,7 +6358,7 @@ 您想 优化 您的 个人投资策略吗? apps/client/src/app/pages/public/public-page.html - 234 + 233 @@ -6662,7 +6690,7 @@ 错误 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 697 + 713 @@ -6714,7 +6742,7 @@ apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 592 + 599 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6746,7 +6774,7 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 345 + 341 apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.html @@ -6754,7 +6782,7 @@ libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 46 + 47 libs/ui/src/lib/i18n.ts @@ -6766,7 +6794,7 @@ 关闭 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 594 + 601 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -6792,13 +6820,17 @@ apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html 68 + + apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html + 127 + apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html 107 apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 347 + 343 libs/ui/src/lib/i18n.ts @@ -6810,7 +6842,7 @@ 角色 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 14 + 33 @@ -6834,7 +6866,7 @@ 投资组合快照 apps/client/src/app/components/admin-jobs/admin-jobs.html - 56 + 65 @@ -6901,6 +6933,14 @@ 42 + + has been copied to the clipboard + has been copied to the clipboard + + libs/ui/src/lib/value/value.component.ts + 180 + + From the beginning 从头开始 @@ -7066,7 +7106,7 @@ 设置 API 密钥 apps/client/src/app/components/admin-settings/admin-settings.component.html - 171 + 188 @@ -7102,7 +7142,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 45 + 44 libs/common/src/lib/routes/routes.ts @@ -7118,7 +7158,7 @@ apps/client/src/app/pages/resources/resources-page.component.ts - 34 + 33 libs/common/src/lib/routes/routes.ts @@ -7172,7 +7212,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.html - 135 + 152 @@ -7180,7 +7220,7 @@ 每日请求 apps/client/src/app/components/admin-settings/admin-settings.component.html - 137 + 154 @@ -7188,7 +7228,7 @@ 移除 API 密钥 apps/client/src/app/components/admin-settings/admin-settings.component.html - 161 + 178 @@ -7196,7 +7236,7 @@ 您确定要删除此 API 密钥吗? apps/client/src/app/components/admin-settings/admin-settings.component.ts - 127 + 133 @@ -7216,7 +7256,7 @@ apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 98 + 117 @@ -7288,7 +7328,7 @@ 保存 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 603 + 610 apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html @@ -7324,11 +7364,11 @@ apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html - 356 + 352 libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html - 48 + 49 @@ -7340,7 +7380,7 @@ apps/client/src/app/components/user-account-access/user-account-access.component.ts - 260 + 257 @@ -7364,7 +7404,7 @@ 请输入您的 Ghostfolio API 密钥。 apps/client/src/app/components/admin-settings/admin-settings.component.ts - 146 + 152 @@ -7380,7 +7420,7 @@ AI 提示已复制到剪贴板 apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 201 + 199 @@ -7396,7 +7436,7 @@ 延迟 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7404,7 +7444,7 @@ 即时 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7412,7 +7452,7 @@ 默认市场价格 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 481 + 484 @@ -7420,7 +7460,7 @@ 模式 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 518 + 521 @@ -7428,7 +7468,7 @@ 选择器 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 534 + 537 @@ -7436,7 +7476,7 @@ HTTP 请求标头 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html - 494 + 497 @@ -7444,7 +7484,7 @@ 收盘 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 225 + 231 @@ -7452,7 +7492,7 @@ 实时 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 229 + 235 @@ -7460,7 +7500,7 @@ 打开 Duck.ai apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts - 202 + 200 @@ -7480,7 +7520,7 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 @@ -7492,7 +7532,7 @@ apps/client/src/app/components/home-overview/home-overview.component.ts - 55 + 54 libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -7500,11 +7540,11 @@ libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 368 + 377 libs/ui/src/lib/treemap-chart/treemap-chart.component.ts - 381 + 390 @@ -7608,11 +7648,11 @@ 安全令牌 apps/client/src/app/components/admin-users/admin-users.component.ts - 236 + 235 apps/client/src/app/components/user-account-access/user-account-access.component.ts - 170 + 167 @@ -7620,7 +7660,7 @@ 您确定要为此用户生成新的安全令牌吗? apps/client/src/app/components/admin-users/admin-users.component.ts - 241 + 240 @@ -7628,7 +7668,7 @@ 查找账户、持仓或页面... libs/ui/src/lib/assistant/assistant.component.ts - 151 + 115 @@ -7693,7 +7733,7 @@ () 已在使用中。 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 633 + 649 @@ -7701,7 +7741,7 @@ 在更新到 () 时发生错误。 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 641 + 657 @@ -7765,7 +7805,7 @@ 某人 apps/client/src/app/pages/public/public-page.component.ts - 59 + 61 @@ -7797,7 +7837,7 @@ 您确定要删除此项目吗? libs/ui/src/lib/benchmark/benchmark.component.ts - 144 + 139 @@ -7838,7 +7878,7 @@ 演示用户账户已同步。 apps/client/src/app/components/admin-overview/admin-overview.component.ts - 274 + 277 @@ -7873,7 +7913,7 @@ 150 - + Fee Ratio 费率 @@ -7881,17 +7921,17 @@ 152 - - The fees do exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - 费用超过了您初始投资的 ${thresholdMax}% (${feeRatio}%) + + The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + 费用已超过您总投资金额的 ${thresholdMax}%(${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 154 - - The fees do not exceed ${thresholdMax}% of your initial investment (${feeRatio}%) - 费用未超过您初始投资的 ${thresholdMax}% (${feeRatio}%) + + The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + 费用未超过您总投资金额的 ${thresholdMax}%(${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 158 @@ -8052,7 +8092,7 @@ 当前月份 apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts - 195 + 201 @@ -8060,11 +8100,11 @@ 新增 apps/client/src/app/components/admin-settings/admin-settings.component.html - 67 + 79 apps/client/src/app/pages/admin/admin-page.component.ts - 56 + 53 @@ -8221,7 +8261,7 @@ 您真的想要生成一个新的安全令牌吗? apps/client/src/app/components/user-account-access/user-account-access.component.ts - 175 + 172 @@ -8245,7 +8285,7 @@ 股票 apps/client/src/app/components/markets/markets.component.ts - 52 + 51 apps/client/src/app/pages/features/features-page.html @@ -8257,7 +8297,7 @@ 加密货币 apps/client/src/app/components/markets/markets.component.ts - 53 + 52 apps/client/src/app/pages/features/features-page.html @@ -8277,7 +8317,7 @@ 管理资产概况 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html - 466 + 471 @@ -8697,7 +8737,7 @@ 注册日期 apps/client/src/app/components/user-detail-dialog/user-detail-dialog.html - 26 + 45 diff --git a/apps/client/src/styles.scss b/apps/client/src/styles.scss index b7a031bfa..f5a4e9c80 100644 --- a/apps/client/src/styles.scss +++ b/apps/client/src/styles.scss @@ -2,7 +2,7 @@ @import './styles/table'; @import './styles/variables'; -@import 'svgmap/dist/svgMap'; +@import 'svgmap/style.min'; :root { --dark-background: rgb(25, 25, 25); @@ -546,6 +546,10 @@ ngx-skeleton-loader { padding: 9px 24px !important; } +.no-height { + height: unset !important; +} + .no-min-width { min-width: unset !important; } diff --git a/libs/common/src/lib/calculation-helper.ts b/libs/common/src/lib/calculation-helper.ts index d67384a30..2097fa52a 100644 --- a/libs/common/src/lib/calculation-helper.ts +++ b/libs/common/src/lib/calculation-helper.ts @@ -9,7 +9,7 @@ import { subDays, subYears } from 'date-fns'; -import { isNumber } from 'lodash'; +import { isFinite, isNumber } from 'lodash'; import { resetHours } from './helper'; import { DateRange } from './types'; @@ -28,7 +28,7 @@ export function getAnnualizedPerformancePercent({ exponent ); - if (!isNaN(growthFactor)) { + if (isFinite(growthFactor)) { return new Big(growthFactor).minus(1); } } @@ -36,14 +36,16 @@ export function getAnnualizedPerformancePercent({ return new Big(0); } -export function getIntervalFromDateRange( - aDateRange: DateRange, - portfolioStart = new Date(0) -) { - let endDate = endOfDay(new Date()); - let startDate = portfolioStart; +export function getIntervalFromDateRange(params: { + dateRange: DateRange; + endDate?: Date; + startDate?: Date; +}) { + const { dateRange } = params; + let endDate = params.endDate ?? endOfDay(new Date()); + let startDate = params.startDate ?? new Date(0); - switch (aDateRange) { + switch (dateRange) { case '1d': startDate = max([startDate, subDays(resetHours(new Date()), 1)]); break; @@ -75,8 +77,8 @@ export function getIntervalFromDateRange( break; default: // '2024', '2023', '2022', etc. - endDate = endOfYear(new Date(aDateRange)); - startDate = max([startDate, new Date(aDateRange)]); + endDate = endOfYear(new Date(dateRange)); + startDate = max([startDate, new Date(dateRange)]); } return { endDate, startDate }; diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index b558ccc42..08fa2f030 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -51,6 +51,14 @@ export const ASSET_CLASS_MAPPING = new Map([ [AssetClass.REAL_ESTATE, []] ]); +export const BULL_BOARD_COOKIE_NAME = 'bull_board_token'; + +/** + * WARNING: This route is mirrored in `apps/client/proxy.conf.json`. + * If you update this value, you must also update the proxy configuration. + */ +export const BULL_BOARD_ROUTE = '/admin/queues'; + export const CACHE_TTL_NO_CACHE = 1; export const CACHE_TTL_INFINITE = 0; @@ -80,6 +88,11 @@ export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_TIMEOUT = 30000; export const DEFAULT_REDACTED_PATHS = [ 'accounts[*].balance', + 'accounts[*].balanceInBaseCurrency', + 'accounts[*].comment', + 'accounts[*].dividendInBaseCurrency', + 'accounts[*].interestInBaseCurrency', + 'accounts[*].value', 'accounts[*].valueInBaseCurrency', 'activities[*].account.balance', 'activities[*].account.comment', @@ -199,6 +212,7 @@ export const PROPERTY_BETTER_UPTIME_MONITOR_ID = 'BETTER_UPTIME_MONITOR_ID'; export const PROPERTY_COUNTRIES_OF_SUBSCRIBERS = 'COUNTRIES_OF_SUBSCRIBERS'; export const PROPERTY_COUPONS = 'COUPONS'; export const PROPERTY_CURRENCIES = 'CURRENCIES'; +export const PROPERTY_CUSTOM_CRYPTOCURRENCIES = 'CUSTOM_CRYPTOCURRENCIES'; export const PROPERTY_DATA_SOURCE_MAPPING = 'DATA_SOURCE_MAPPING'; export const PROPERTY_DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS = 'DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS'; diff --git a/libs/common/src/lib/dtos/create-asset-profile.dto.ts b/libs/common/src/lib/dtos/create-asset-profile.dto.ts index 80d45ba42..85ad73cc0 100644 --- a/libs/common/src/lib/dtos/create-asset-profile.dto.ts +++ b/libs/common/src/lib/dtos/create-asset-profile.dto.ts @@ -5,7 +5,6 @@ import { IsArray, IsBoolean, IsEnum, - IsObject, IsOptional, IsString, IsUrl @@ -66,10 +65,6 @@ export class CreateAssetProfileDto { @IsString() name?: string; - @IsObject() - @IsOptional() - scraperConfiguration?: Prisma.InputJsonObject; - @IsArray() @IsOptional() sectors?: Prisma.InputJsonArray; @@ -77,12 +72,6 @@ export class CreateAssetProfileDto { @IsString() symbol: string; - @IsObject() - @IsOptional() - symbolMapping?: { - [dataProvider: string]: string; - }; - @IsOptional() @IsUrl({ protocols: ['https'], diff --git a/libs/common/src/lib/helper.spec.ts b/libs/common/src/lib/helper.spec.ts index 25779cf39..a339c6dab 100644 --- a/libs/common/src/lib/helper.spec.ts +++ b/libs/common/src/lib/helper.spec.ts @@ -25,7 +25,7 @@ describe('Helper', () => { it('Get decimal number with group (dot notation)', () => { expect( - extractNumberFromString({ locale: 'de-CH', value: '99’999.99' }) + extractNumberFromString({ locale: 'de-CH', value: `99'999.99` }) ).toEqual(99999.99); }); @@ -54,12 +54,12 @@ describe('Helper', () => { }); it('Get de-CH number format group', () => { - expect(getNumberFormatGroup('de-CH')).toEqual('’'); + expect(getNumberFormatGroup('de-CH')).toEqual(`'`); }); it('Get de-CH number format group when it is default', () => { languageGetter.mockReturnValue('de-CH'); - expect(getNumberFormatGroup()).toEqual('’'); + expect(getNumberFormatGroup()).toEqual(`'`); }); it('Get de-DE number format group', () => { diff --git a/libs/common/src/lib/interfaces/lookup-item.interface.ts b/libs/common/src/lib/interfaces/lookup-item.interface.ts index fa91ed690..6cedeca09 100644 --- a/libs/common/src/lib/interfaces/lookup-item.interface.ts +++ b/libs/common/src/lib/interfaces/lookup-item.interface.ts @@ -7,7 +7,7 @@ export interface LookupItem { assetSubClass: AssetSubClass; currency: string; dataProviderInfo: DataProviderInfo; - dataSource: DataSource; + dataSource: DataSource | null; name: string; symbol: string; } diff --git a/libs/common/src/lib/interfaces/portfolio-performance.interface.ts b/libs/common/src/lib/interfaces/portfolio-performance.interface.ts index c0c3802d8..0698004d5 100644 --- a/libs/common/src/lib/interfaces/portfolio-performance.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-performance.interface.ts @@ -7,4 +7,5 @@ export interface PortfolioPerformance { netPerformancePercentageWithCurrencyEffect: number; netPerformanceWithCurrencyEffect: number; totalInvestment: number; + totalInvestmentValueWithCurrencyEffect: number; } diff --git a/libs/common/src/lib/interfaces/portfolio-position.interface.ts b/libs/common/src/lib/interfaces/portfolio-position.interface.ts index 620cc00e9..c4ef2e3dc 100644 --- a/libs/common/src/lib/interfaces/portfolio-position.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-position.interface.ts @@ -3,19 +3,52 @@ import { Market, MarketAdvanced } from '@ghostfolio/common/types'; import { AssetClass, AssetSubClass, DataSource, Tag } from '@prisma/client'; import { Country } from './country.interface'; +import { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface'; import { Holding } from './holding.interface'; import { Sector } from './sector.interface'; export interface PortfolioPosition { activitiesCount: number; allocationInPercentage: number; + + /** @deprecated */ assetClass?: AssetClass; + + /** @deprecated */ assetClassLabel?: string; + + assetProfile: Pick< + EnhancedSymbolProfile, + | 'assetClass' + | 'assetSubClass' + | 'countries' + | 'currency' + | 'dataSource' + | 'holdings' + | 'name' + | 'sectors' + | 'symbol' + | 'url' + > & { + assetClassLabel?: string; + assetSubClassLabel?: string; + }; + + /** @deprecated */ assetSubClass?: AssetSubClass; + + /** @deprecated */ assetSubClassLabel?: string; + + /** @deprecated */ countries: Country[]; + + /** @deprecated */ currency: string; + + /** @deprecated */ dataSource: DataSource; + dateOfFirstActivity: Date; dividend: number; exchange?: string; @@ -23,24 +56,38 @@ export interface PortfolioPosition { grossPerformancePercent: number; grossPerformancePercentWithCurrencyEffect: number; grossPerformanceWithCurrencyEffect: number; + + /** @deprecated */ holdings: Holding[]; + investment: number; marketChange?: number; marketChangePercent?: number; marketPrice: number; markets?: { [key in Market]: number }; marketsAdvanced?: { [key in MarketAdvanced]: number }; + + /** @deprecated */ name: string; + netPerformance: number; netPerformancePercent: number; netPerformancePercentWithCurrencyEffect: number; netPerformanceWithCurrencyEffect: number; quantity: number; + + /** @deprecated */ sectors: Sector[]; + + /** @deprecated */ symbol: string; + tags?: Tag[]; type?: string; + + /** @deprecated */ url?: string; + valueInBaseCurrency?: number; valueInPercentage?: number; } diff --git a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts index ccf94dcf7..8db6b39bb 100644 --- a/libs/common/src/lib/interfaces/portfolio-summary.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-summary.interface.ts @@ -6,7 +6,6 @@ export interface PortfolioSummary extends PortfolioPerformance { annualizedPerformancePercent: number; annualizedPerformancePercentWithCurrencyEffect: number; cash: number; - committedFunds: number; dateOfFirstActivity: Date; dividendInBaseCurrency: number; emergencyFund: { diff --git a/libs/common/src/lib/interfaces/responses/export-response.interface.ts b/libs/common/src/lib/interfaces/responses/export-response.interface.ts index 8b1697ca4..fa592faf2 100644 --- a/libs/common/src/lib/interfaces/responses/export-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/export-response.interface.ts @@ -27,7 +27,12 @@ export interface ExportResponse { > & { dataSource: DataSource; date: string; symbol: string })[]; assetProfiles: (Omit< SymbolProfile, - 'createdAt' | 'id' | 'updatedAt' | 'userId' + | 'createdAt' + | 'id' + | 'scraperConfiguration' + | 'symbolMapping' + | 'updatedAt' + | 'userId' > & { marketData: MarketData[]; })[]; diff --git a/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts b/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts index 4a087ad16..eae14cec6 100644 --- a/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts @@ -14,16 +14,31 @@ export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 { [symbol: string]: Pick< PortfolioPosition, | 'allocationInPercentage' + + /** @deprecated */ | 'assetClass' + | 'assetProfile' + + /** @deprecated */ | 'countries' | 'currency' + + /** @deprecated */ | 'dataSource' | 'dateOfFirstActivity' | 'markets' + + /** @deprecated */ | 'name' | 'netPerformancePercentWithCurrencyEffect' + + /** @deprecated */ | 'sectors' + + /** @deprecated */ | 'symbol' + + /** @deprecated */ | 'url' | 'valueInBaseCurrency' | 'valueInPercentage' diff --git a/libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts b/libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts index bdef3e169..60a76d468 100644 --- a/libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts +++ b/libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts @@ -9,7 +9,7 @@ export interface XRayRulesSettings { EconomicMarketClusterRiskDevelopedMarkets?: RuleSettings; EconomicMarketClusterRiskEmergingMarkets?: RuleSettings; EmergencyFundSetup?: RuleSettings; - FeeRatioInitialInvestment?: RuleSettings; + FeeRatioTotalInvestmentVolume?: RuleSettings; RegionalMarketClusterRiskAsiaPacific?: RuleSettings; RegionalMarketClusterRiskEmergingMarkets?: RuleSettings; RegionalMarketClusterRiskEurope?: RuleSettings; diff --git a/libs/common/src/lib/permissions.ts b/libs/common/src/lib/permissions.ts index cb4eb175b..f9cb19562 100644 --- a/libs/common/src/lib/permissions.ts +++ b/libs/common/src/lib/permissions.ts @@ -4,15 +4,16 @@ import { Role } from '@prisma/client'; export const permissions = { accessAdminControl: 'accessAdminControl', + accessAdminControlBullBoard: 'accessAdminControlBullBoard', accessAssistant: 'accessAssistant', accessHoldingsChart: 'accessHoldingsChart', createAccess: 'createAccess', createAccount: 'createAccount', createAccountBalance: 'createAccountBalance', + createActivity: 'createActivity', createApiKey: 'createApiKey', createMarketData: 'createMarketData', createMarketDataOfOwnAssetProfile: 'createMarketDataOfOwnAssetProfile', - createOrder: 'createOrder', createOwnTag: 'createOwnTag', createPlatform: 'createPlatform', createTag: 'createTag', @@ -21,8 +22,8 @@ export const permissions = { deleteAccess: 'deleteAccess', deleteAccount: 'deleteAccount', deleteAccountBalance: 'deleteAccountBalance', + deleteActivity: 'deleteActivity', deleteAuthDevice: 'deleteAuthDevice', - deleteOrder: 'deleteOrder', deleteOwnUser: 'deleteOwnUser', deletePlatform: 'deletePlatform', deleteTag: 'deleteTag', @@ -53,10 +54,10 @@ export const permissions = { toggleReadOnlyMode: 'toggleReadOnlyMode', updateAccount: 'updateAccount', updateAccess: 'updateAccess', + updateActivity: 'updateActivity', updateAuthDevice: 'updateAuthDevice', updateMarketData: 'updateMarketData', updateMarketDataOfOwnAssetProfile: 'updateMarketDataOfOwnAssetProfile', - updateOrder: 'updateOrder', updateOwnAccessToken: 'updateOwnAccessToken', updatePlatform: 'updatePlatform', updateTag: 'updateTag', @@ -74,19 +75,19 @@ export function getPermissions(aRole: Role): string[] { permissions.createAccess, permissions.createAccount, permissions.createAccountBalance, + permissions.createActivity, permissions.createWatchlistItem, permissions.deleteAccountBalance, permissions.deleteWatchlistItem, permissions.createMarketData, permissions.createMarketDataOfOwnAssetProfile, - permissions.createOrder, permissions.createOwnTag, permissions.createPlatform, permissions.createTag, permissions.deleteAccess, permissions.deleteAccount, + permissions.deleteActivity, permissions.deleteAuthDevice, - permissions.deleteOrder, permissions.deletePlatform, permissions.deleteTag, permissions.deleteUser, @@ -99,10 +100,10 @@ export function getPermissions(aRole: Role): string[] { permissions.readWatchlist, permissions.updateAccount, permissions.updateAccess, + permissions.updateActivity, permissions.updateAuthDevice, permissions.updateMarketData, permissions.updateMarketDataOfOwnAssetProfile, - permissions.updateOrder, permissions.updatePlatform, permissions.updateTag, permissions.updateUserSettings, @@ -125,15 +126,15 @@ export function getPermissions(aRole: Role): string[] { permissions.createAccess, permissions.createAccount, permissions.createAccountBalance, + permissions.createActivity, permissions.createMarketDataOfOwnAssetProfile, - permissions.createOrder, permissions.createOwnTag, permissions.createWatchlistItem, permissions.deleteAccess, permissions.deleteAccount, permissions.deleteAccountBalance, + permissions.deleteActivity, permissions.deleteAuthDevice, - permissions.deleteOrder, permissions.deleteWatchlistItem, permissions.readAiPrompt, permissions.readMarketDataOfOwnAssetProfile, @@ -141,9 +142,9 @@ export function getPermissions(aRole: Role): string[] { permissions.readWatchlist, permissions.updateAccount, permissions.updateAccess, + permissions.updateActivity, permissions.updateAuthDevice, permissions.updateMarketDataOfOwnAssetProfile, - permissions.updateOrder, permissions.updateUserSettings, permissions.updateViewMode ]; @@ -194,7 +195,7 @@ export function hasReadRestrictedAccessPermission({ return false; } - const access = user.accessesGet?.find(({ id }) => { + const access = user?.accessesGet?.find(({ id }) => { return id === impersonationId; }); diff --git a/libs/common/src/lib/personal-finance-tools.ts b/libs/common/src/lib/personal-finance-tools.ts index 6d0a85fb2..063b4254c 100644 --- a/libs/common/src/lib/personal-finance-tools.ts +++ b/libs/common/src/lib/personal-finance-tools.ts @@ -33,6 +33,15 @@ export const personalFinanceTools: Product[] = [ origin: 'Switzerland', slogan: 'Simplicity for Complex Wealth' }, + { + founded: 2018, + hasFreePlan: false, + hasSelfHostingAbility: false, + key: 'altruist', + name: 'Altruist', + origin: 'United States', + slogan: 'The wealth platform built for independent advisors' + }, { founded: 2023, hasFreePlan: false, @@ -116,6 +125,17 @@ export const personalFinanceTools: Product[] = [ origin: 'Switzerland', slogan: 'Schweizer Budget App für einfache & smarte Budgetplanung' }, + { + founded: 2015, + hasFreePlan: true, + hasSelfHostingAbility: false, + key: 'boldin', + name: 'Boldin', + note: 'Originally named as NewRetirement', + origin: 'United States', + pricingPerYear: '$144', + slogan: 'Take control with retirement planning tools that begin with you' + }, { key: 'budgetpulse', name: 'BudgetPulse', @@ -841,6 +861,16 @@ export const personalFinanceTools: Product[] = [ pricingPerYear: '$108', slogan: 'Build Financial Plans You Love.' }, + { + founded: 2022, + hasFreePlan: false, + hasSelfHostingAbility: false, + key: 'prostocktracker', + name: 'Pro Stock Tracker', + origin: 'United Kingdom', + pricingPerYear: '$60', + slogan: 'The stock portfolio tracker built for long-term investors' + }, { founded: 2015, hasSelfHostingAbility: false, @@ -1014,6 +1044,16 @@ export const personalFinanceTools: Product[] = [ regions: ['Austria', 'Germany', 'Switzerland'], slogan: 'Dein Vermögen immer im Blick' }, + { + founded: 2025, + hasFreePlan: false, + hasSelfHostingAbility: false, + key: 'turbobulls', + name: 'Turbobulls', + origin: 'Romania', + pricingPerYear: '€39.99', + slogan: 'Your complete financial dashboard. Actually private.' + }, { hasFreePlan: true, hasSelfHostingAbility: false, diff --git a/libs/common/src/lib/routes/routes.ts b/libs/common/src/lib/routes/routes.ts index 53ecd104e..2785efdde 100644 --- a/libs/common/src/lib/routes/routes.ts +++ b/libs/common/src/lib/routes/routes.ts @@ -15,7 +15,7 @@ if (typeof window !== 'undefined') { }; } -export const internalRoutes: Record = { +export const internalRoutes = { account: { path: 'account', routerLink: ['/account'], @@ -169,9 +169,9 @@ export const internalRoutes: Record = { }, title: $localize`Overview` } -}; +} satisfies Record; -export const publicRoutes: Record = { +export const publicRoutes = { about: { path: $localize`:kebab-case@@routes.about:about`, routerLink: ['/' + $localize`:kebab-case@@routes.about:about`], @@ -336,4 +336,4 @@ export const publicRoutes: Record = { path: $localize`:kebab-case@@routes.start:start`, routerLink: ['/' + $localize`:kebab-case@@routes.start:start`] } -}; +} satisfies Record; diff --git a/libs/common/src/lib/types/market-data-preset.type.ts b/libs/common/src/lib/types/market-data-preset.type.ts index 0dbf914fa..2622feac3 100644 --- a/libs/common/src/lib/types/market-data-preset.type.ts +++ b/libs/common/src/lib/types/market-data-preset.type.ts @@ -2,4 +2,5 @@ export type MarketDataPreset = | 'BENCHMARKS' | 'CURRENCIES' | 'ETF_WITHOUT_COUNTRIES' - | 'ETF_WITHOUT_SECTORS'; + | 'ETF_WITHOUT_SECTORS' + | 'NO_ACTIVITIES'; diff --git a/libs/ui/src/lib/account-balances/account-balances.component.html b/libs/ui/src/lib/account-balances/account-balances.component.html index caef922ed..29037a985 100644 --- a/libs/ui/src/lib/account-balances/account-balances.component.html +++ b/libs/ui/src/lib/account-balances/account-balances.component.html @@ -12,7 +12,7 @@ Date
    + Name @@ -102,7 +114,12 @@ - + Asset Profiles @@ -193,13 +210,13 @@

    Platforms

    - +

    Tags

    - +
    diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.ts b/apps/client/src/app/components/admin-settings/admin-settings.component.ts index 446221058..d030abb82 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.ts +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -22,20 +22,24 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, - OnDestroy, - OnInit + DestroyRef, + OnInit, + ViewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatMenuModule } from '@angular/material/menu'; import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { ellipsisHorizontal, trashOutline } from 'ionicons/icons'; +import { get } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { catchError, filter, of, Subject, takeUntil } from 'rxjs'; +import { catchError, filter, of } from 'rxjs'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -52,6 +56,7 @@ import { catchError, filter, of, Subject, takeUntil } from 'rxjs'; MatCardModule, MatMenuModule, MatProgressBarModule, + MatSortModule, MatTableModule, NgxSkeletonLoaderModule, RouterModule @@ -60,7 +65,9 @@ import { catchError, filter, of, Subject, takeUntil } from 'rxjs'; styleUrls: ['./admin-settings.component.scss'], templateUrl: './admin-settings.component.html' }) -export class GfAdminSettingsComponent implements OnDestroy, OnInit { +export class GfAdminSettingsComponent implements OnInit { + @ViewChild(MatSort) sort: MatSort; + public dataSource = new MatTableDataSource(); public defaultDateFormat: string; public displayedColumns = [ @@ -74,14 +81,13 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { public isGhostfolioApiKeyValid: boolean; public isLoading = false; public pricingUrl: string; - - private unsubscribeSubject = new Subject(); - private user: User; + public user: User; public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, private notificationService: NotificationService, private userService: UserService ) { @@ -90,7 +96,7 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { public ngOnInit() { this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { if (state?.user) { this.user = state.user; @@ -147,11 +153,6 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private initialize() { this.isLoading = true; @@ -159,13 +160,15 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { this.adminService .fetchAdminData() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ dataProviders, settings }) => { const filteredProviders = dataProviders.filter(({ dataSource }) => { return dataSource !== 'MANUAL'; }); this.dataSource = new MatTableDataSource(filteredProviders); + this.dataSource.sort = this.sort; + this.dataSource.sortingDataAccessor = get; const ghostfolioApiKey = settings[ PROPERTY_API_KEY_GHOSTFOLIO @@ -185,7 +188,7 @@ export class GfAdminSettingsComponent implements OnDestroy, OnInit { filter((status) => { return status !== null; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe((status) => { this.ghostfolioApiStatus = status; diff --git a/apps/client/src/app/components/admin-tag/admin-tag.component.html b/apps/client/src/app/components/admin-tag/admin-tag.component.html index 98919abd8..463a817ff 100644 --- a/apps/client/src/app/components/admin-tag/admin-tag.component.html +++ b/apps/client/src/app/components/admin-tag/admin-tag.component.html @@ -45,7 +45,11 @@ Activities
    - {{ element.activityCount }} + - + @@ -37,7 +37,7 @@
    @@ -48,7 +48,7 @@
    - {{ accountCurrency }} + {{ accountCurrency() }}
    @@ -58,7 +58,7 @@
    - @if (showActions) { + @if (showActions()) {
    diff --git a/libs/ui/src/lib/account-balances/account-balances.component.ts b/libs/ui/src/lib/account-balances/account-balances.component.ts index 608ee1c75..7b26263b0 100644 --- a/libs/ui/src/lib/account-balances/account-balances.component.ts +++ b/libs/ui/src/lib/account-balances/account-balances.component.ts @@ -10,12 +10,12 @@ import { ChangeDetectionStrategy, Component, EventEmitter, - Input, OnChanges, - OnDestroy, OnInit, Output, - ViewChild + inject, + input, + viewChild } from '@angular/core'; import { FormGroup, @@ -39,8 +39,7 @@ import { ellipsisHorizontal, trashOutline } from 'ionicons/icons'; -import { get } from 'lodash'; -import { Subject } from 'rxjs'; +import { get, isNil } from 'lodash'; import { GfValueComponent } from '../value'; @@ -63,50 +62,44 @@ import { GfValueComponent } from '../value'; styleUrls: ['./account-balances.component.scss'], templateUrl: './account-balances.component.html' }) -export class GfAccountBalancesComponent - implements OnChanges, OnDestroy, OnInit -{ - @Input() accountBalances: AccountBalancesResponse['balances']; - @Input() accountCurrency: string; - @Input() accountId: string; - @Input() locale = getLocale(); - @Input() showActions = true; - +export class GfAccountBalancesComponent implements OnChanges, OnInit { @Output() accountBalanceCreated = new EventEmitter(); @Output() accountBalanceDeleted = new EventEmitter(); - @ViewChild(MatSort) sort: MatSort; + public readonly accountBalances = + input.required(); + public readonly accountCurrency = input.required(); + public readonly accountId = input.required(); + public readonly displayedColumns: string[] = ['date', 'value', 'actions']; + public readonly locale = input(getLocale()); + public readonly showActions = input(true); + public readonly sort = viewChild(MatSort); public accountBalanceForm = new FormGroup({ - balance: new FormControl(0, Validators.required), - date: new FormControl(new Date(), Validators.required) + balance: new FormControl(0, (control) => Validators.required(control)), + date: new FormControl(new Date(), (control) => Validators.required(control)) }); public dataSource = new MatTableDataSource< AccountBalancesResponse['balances'][0] >(); - public displayedColumns: string[] = ['date', 'value', 'actions']; - public Validators = Validators; - - private unsubscribeSubject = new Subject(); + private dateAdapter = inject>(DateAdapter); + private notificationService = inject(NotificationService); - public constructor( - private dateAdapter: DateAdapter, - private notificationService: NotificationService - ) { + public constructor() { addIcons({ calendarClearOutline, ellipsisHorizontal, trashOutline }); } public ngOnInit() { - this.dateAdapter.setLocale(this.locale); + this.dateAdapter.setLocale(this.locale()); } public ngOnChanges() { - if (this.accountBalances) { - this.dataSource = new MatTableDataSource(this.accountBalances); + if (this.accountBalances()) { + this.dataSource = new MatTableDataSource(this.accountBalances()); - this.dataSource.sort = this.sort; + this.dataSource.sort = this.sort(); this.dataSource.sortingDataAccessor = get; } } @@ -122,10 +115,16 @@ export class GfAccountBalancesComponent } public async onSubmitAccountBalance() { + const { balance, date } = this.accountBalanceForm.value; + + if (isNil(balance) || !date) { + return; + } + const accountBalance: CreateAccountBalanceDto = { - accountId: this.accountId, - balance: this.accountBalanceForm.get('balance').value, - date: format(this.accountBalanceForm.get('date').value, DATE_FORMAT) + balance, + accountId: this.accountId(), + date: format(date, DATE_FORMAT) }; try { @@ -141,9 +140,4 @@ export class GfAccountBalancesComponent this.accountBalanceCreated.emit(accountBalance); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/libs/ui/src/lib/accounts-table/accounts-table.component.ts b/libs/ui/src/lib/accounts-table/accounts-table.component.ts index d486b775f..99e68c679 100644 --- a/libs/ui/src/lib/accounts-table/accounts-table.component.ts +++ b/libs/ui/src/lib/accounts-table/accounts-table.component.ts @@ -53,7 +53,7 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; templateUrl: './accounts-table.component.html' }) export class GfAccountsTableComponent { - public readonly accounts = input.required(); + public readonly accounts = input.required(); public readonly activitiesCount = input(); public readonly baseCurrency = input(); public readonly hasPermissionToOpenDetails = input(true); diff --git a/libs/ui/src/lib/activities-filter/activities-filter.component.html b/libs/ui/src/lib/activities-filter/activities-filter.component.html index fd22ed351..d87ce16ce 100644 --- a/libs/ui/src/lib/activities-filter/activities-filter.component.html +++ b/libs/ui/src/lib/activities-filter/activities-filter.component.html @@ -10,7 +10,7 @@ [removable]="true" (removed)="onRemoveFilter(filter)" > - {{ filter.label | gfSymbol }} + {{ filter.label ?? '' | gfSymbol }} @@ -23,7 +23,7 @@ [matAutocomplete]="autocomplete" [matChipInputFor]="chipList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes" - [placeholder]="placeholder" + [placeholder]="placeholder()" (matChipInputTokenEnd)="onAddFilter($event)" /> @@ -35,7 +35,7 @@ @for (filter of filterGroup.filters; track filter) { - {{ filter.label | gfSymbol }} + {{ filter.label ?? '' | gfSymbol }} } @@ -46,7 +46,7 @@ disabled mat-icon-button matSuffix - [ngClass]="{ 'd-none': !isLoading }" + [ngClass]="{ 'd-none': !isLoading() }" > diff --git a/libs/ui/src/lib/activities-filter/activities-filter.component.ts b/libs/ui/src/lib/activities-filter/activities-filter.component.ts index 34f883c67..6b58e6aec 100644 --- a/libs/ui/src/lib/activities-filter/activities-filter.component.ts +++ b/libs/ui/src/lib/activities-filter/activities-filter.component.ts @@ -7,15 +7,16 @@ import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, + DestroyRef, ElementRef, - EventEmitter, Input, OnChanges, - OnDestroy, - Output, SimpleChanges, - ViewChild + ViewChild, + input, + output } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { MatAutocomplete, @@ -30,8 +31,7 @@ import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { closeOutline, searchOutline } from 'ionicons/icons'; import { groupBy } from 'lodash'; -import { BehaviorSubject, Observable, Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { BehaviorSubject } from 'rxjs'; import { translate } from '../i18n'; @@ -53,28 +53,26 @@ import { translate } from '../i18n'; styleUrls: ['./activities-filter.component.scss'], templateUrl: './activities-filter.component.html' }) -export class GfActivitiesFilterComponent implements OnChanges, OnDestroy { +export class GfActivitiesFilterComponent implements OnChanges { @Input() allFilters: Filter[]; - @Input() isLoading: boolean; - @Input() placeholder: string; - @Output() valueChanged = new EventEmitter(); + @ViewChild('autocomplete') protected matAutocomplete: MatAutocomplete; + @ViewChild('searchInput') protected searchInput: ElementRef; - @ViewChild('autocomplete') matAutocomplete: MatAutocomplete; - @ViewChild('searchInput') searchInput: ElementRef; + public readonly isLoading = input.required(); + public readonly placeholder = input.required(); + public readonly valueChanged = output(); - public filterGroups$: Subject = new BehaviorSubject([]); - public filters$: Subject = new BehaviorSubject([]); - public filters: Observable = this.filters$.asObservable(); - public searchControl = new FormControl(undefined); - public selectedFilters: Filter[] = []; - public separatorKeysCodes: number[] = [ENTER, COMMA]; + protected readonly filterGroups$ = new BehaviorSubject([]); + protected readonly searchControl = new FormControl( + null + ); + protected selectedFilters: Filter[] = []; + protected readonly separatorKeysCodes: number[] = [ENTER, COMMA]; - private unsubscribeSubject = new Subject(); - - public constructor() { + public constructor(private destroyRef: DestroyRef) { this.searchControl.valueChanges - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((filterOrSearchTerm) => { if (filterOrSearchTerm) { const searchTerm = @@ -97,41 +95,39 @@ export class GfActivitiesFilterComponent implements OnChanges, OnDestroy { } } - public onAddFilter({ input, value }: MatChipInputEvent) { + public onAddFilter({ chipInput, value }: MatChipInputEvent) { if (value?.trim()) { this.updateFilters(); } // Reset the input value - if (input) { - input.value = ''; + if (chipInput.inputElement) { + chipInput.inputElement.value = ''; } - this.searchControl.setValue(undefined); + this.searchControl.setValue(null); } public onRemoveFilter(aFilter: Filter) { - this.selectedFilters = this.selectedFilters.filter((filter) => { - return filter.id !== aFilter.id; + this.selectedFilters = this.selectedFilters.filter(({ id }) => { + return id !== aFilter.id; }); this.updateFilters(); } public onSelectFilter(event: MatAutocompleteSelectedEvent) { - this.selectedFilters.push( - this.allFilters.find((filter) => { - return filter.id === event.option.value; - }) - ); + const filter = this.allFilters.find(({ id }) => { + return id === event.option.value; + }); + + if (filter) { + this.selectedFilters.push(filter); + } + this.updateFilters(); this.searchInput.nativeElement.value = ''; - this.searchControl.setValue(undefined); - } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); + this.searchControl.setValue(null); } private getGroupedFilters(searchTerm?: string) { @@ -139,23 +135,23 @@ export class GfActivitiesFilterComponent implements OnChanges, OnDestroy { this.allFilters .filter((filter) => { // Filter selected filters - return !this.selectedFilters.some((selectedFilter) => { - return selectedFilter.id === filter.id; + return !this.selectedFilters.some(({ id }) => { + return id === filter.id; }); }) .filter((filter) => { if (searchTerm) { // Filter by search term return filter.label - .toLowerCase() + ?.toLowerCase() .includes(searchTerm.toLowerCase()); } return filter; }) - .sort((a, b) => a.label?.localeCompare(b.label)), - (filter) => { - return filter.type; + .sort((a, b) => (a.label ?? '').localeCompare(b.label ?? '')), + ({ type }) => { + return type; } ); diff --git a/libs/ui/src/lib/activities-table/activities-table.component.html b/libs/ui/src/lib/activities-table/activities-table.component.html index bdb1e6373..b8fe962d7 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -1,77 +1,101 @@ -@if (hasPermissionToCreateActivity) { -
    - - @if (hasPermissionToExportActivities) { - +
    +
    + @if (hasPermissionToFilterByType) { + + Type + + @for ( + activityType of activityTypesTranslationMap | keyvalue; + track activityType.key + ) { + + {{ activityType.value }} + + } + + } - +
    + + @if (hasPermissionToCreateActivity) { +
    @if (hasPermissionToExportActivities) { + } + + - } - @if (hasPermissionToExportActivities) { + @if (hasPermissionToExportActivities) { + + } + @if (hasPermissionToExportActivities) { + + } +
    - } -
    - -
    -
    -} + +
    + } +
    (); @Output() selectedActivities = new EventEmitter(); @Output() sortChanged = new EventEmitter(); + @Output() typesFilterChanged = new EventEmitter(); @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; + public activityTypesTranslationMap = new Map(); public hasDrafts = false; public hasErrors = false; public isUUID = isUUID; public selectedRows = new SelectionModel(true, []); + public typesFilter = new FormControl([]); public readonly dataSource = input.required< MatTableDataSource | undefined @@ -186,9 +196,15 @@ export class GfActivitiesTableComponent }); private readonly notificationService = inject(NotificationService); - private readonly unsubscribeSubject = new Subject(); - public constructor() { + public constructor(private destroyRef: DestroyRef) { + for (const type of Object.keys(ActivityType) as ActivityType[]) { + this.activityTypesTranslationMap.set( + ActivityType[type], + translate(ActivityType[type]) + ); + } + addIcons({ alertCircleOutline, calendarClearOutline, @@ -209,11 +225,17 @@ export class GfActivitiesTableComponent if (this.showCheckbox()) { this.toggleAllRows(); this.selectedRows.changed - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((selectedRows) => { this.selectedActivities.emit(selectedRows.source.selected); }); } + + this.typesFilter.valueChanges + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((types) => { + this.typesFilterChanged.emit(types ?? []); + }); } public ngAfterViewInit() { @@ -342,9 +364,4 @@ export class GfActivitiesTableComponent this.selectedActivities.emit(this.selectedRows.selected); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts index c2ad2462e..36127a566 100644 --- a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts +++ b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts @@ -7,12 +7,12 @@ import { ChangeDetectorRef, Component, ElementRef, - EventEmitter, HostBinding, Input, OnChanges, - Output, - ViewChild + ViewChild, + inject, + output } from '@angular/core'; import { Params, RouterModule } from '@angular/router'; @@ -33,21 +33,23 @@ export class GfAssistantListItemComponent implements FocusableOption, OnChanges { @HostBinding('attr.tabindex') tabindex = -1; - @HostBinding('class.has-focus') get getHasFocus() { - return this.hasFocus; - } @Input() item: SearchResultItem; - @Output() clicked = new EventEmitter(); - - @ViewChild('link') public linkElement: ElementRef; + @ViewChild('link') public linkElement: ElementRef; public hasFocus = false; public queryParams: Params; public routerLink: string[]; - public constructor(private changeDetectorRef: ChangeDetectorRef) {} + protected readonly clicked = output(); + + private readonly changeDetectorRef = inject(ChangeDetectorRef); + + @HostBinding('class.has-focus') + public get getHasFocus() { + return this.hasFocus; + } public ngOnChanges() { if (this.item?.mode === SearchMode.ACCOUNT) { @@ -65,7 +67,7 @@ export class GfAssistantListItemComponent }; this.routerLink = - internalRoutes.adminControl.subRoutes.marketData.routerLink; + internalRoutes.adminControl.subRoutes.marketData.routerLink ?? []; } else if (this.item?.mode === SearchMode.HOLDING) { this.queryParams = { dataSource: this.item.dataSource, diff --git a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.html b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.html index fd2c4011d..36179b719 100644 --- a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.html +++ b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.html @@ -8,7 +8,7 @@ @if (item && isAsset(item)) {
    {{ item?.symbol | gfSymbol }} · {{ item?.currency }} + >{{ item?.symbol ?? '' | gfSymbol }} · {{ item?.currency }} @if (item?.assetSubClassString) { · {{ item.assetSubClassString }} } diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index 2b0216613..22d276cd3 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -12,16 +12,15 @@ import { ChangeDetectorRef, Component, ElementRef, - EventEmitter, HostListener, Input, OnChanges, OnDestroy, OnInit, - Output, QueryList, ViewChild, - ViewChildren + ViewChildren, + output } from '@angular/core'; import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; @@ -86,37 +85,7 @@ import { templateUrl: './assistant.html' }) export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { - @HostListener('document:keydown', ['$event']) onKeydown( - event: KeyboardEvent - ) { - if (!this.isOpen) { - return; - } - - if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { - for (const item of this.assistantListItems) { - item.removeFocus(); - } - - this.keyManager.onKeydown(event); - - const currentAssistantListItem = this.getCurrentAssistantListItem(); - - if (currentAssistantListItem?.linkElement) { - currentAssistantListItem.linkElement.nativeElement?.scrollIntoView({ - behavior: 'smooth', - block: 'center' - }); - } - } else if (event.key === 'Enter') { - const currentAssistantListItem = this.getCurrentAssistantListItem(); - - if (currentAssistantListItem?.linkElement) { - currentAssistantListItem.linkElement.nativeElement?.click(); - event.stopPropagation(); - } - } - } + public static readonly SEARCH_RESULTS_DEFAULT_LIMIT = 5; @Input() deviceType: string; @Input() hasPermissionToAccessAdminControl: boolean; @@ -124,21 +93,16 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { @Input() hasPermissionToChangeFilters: boolean; @Input() user: User; - @Output() closed = new EventEmitter(); - @Output() dateRangeChanged = new EventEmitter(); - @Output() filtersChanged = new EventEmitter(); - @ViewChild('menuTrigger') menuTriggerElement: MatMenuTrigger; - @ViewChild('search', { static: true }) searchElement: ElementRef; + @ViewChild('search', { static: true }) + searchElement: ElementRef; @ViewChildren(GfAssistantListItemComponent) assistantListItems: QueryList; - public static readonly SEARCH_RESULTS_DEFAULT_LIMIT = 5; - public accounts: AccountWithPlatform[] = []; public assetClasses: Filter[] = []; - public dateRangeFormControl = new FormControl(undefined); + public dateRangeFormControl = new FormControl(null); public dateRangeOptions: DateRangeOption[] = []; public holdings: PortfolioPosition[] = []; public isLoading = { @@ -166,6 +130,10 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }; public tags: Filter[] = []; + protected readonly closed = output(); + protected readonly dateRangeChanged = output(); + protected readonly filtersChanged = output(); + private readonly PRESELECTION_DELAY = 100; private filterTypes: Filter['type'][] = [ @@ -188,6 +156,37 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { addIcons({ closeCircleOutline, closeOutline, searchOutline }); } + @HostListener('document:keydown', ['$event']) + public onKeydown(event: KeyboardEvent) { + if (!this.isOpen) { + return; + } + + if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { + for (const item of this.assistantListItems) { + item.removeFocus(); + } + + this.keyManager.onKeydown(event); + + const currentAssistantListItem = this.getCurrentAssistantListItem(); + + if (currentAssistantListItem?.linkElement) { + currentAssistantListItem.linkElement.nativeElement?.scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); + } + } else if (event.key === 'Enter') { + const currentAssistantListItem = this.getCurrentAssistantListItem(); + + if (currentAssistantListItem?.linkElement) { + currentAssistantListItem.linkElement.nativeElement?.click(); + event.stopPropagation(); + } + } + } + public ngOnInit() { this.assetClasses = Object.keys(AssetClass).map((assetClass) => { return { @@ -482,7 +481,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { .subscribe(({ holdings }) => { this.holdings = holdings .filter(({ assetSubClass }) => { - return !['CASH'].includes(assetSubClass); + return assetSubClass && !['CASH'].includes(assetSubClass); }) .sort((a, b) => { return a.name?.localeCompare(b.name); @@ -499,23 +498,23 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { this.filtersChanged.emit([ { - id: filterValue?.account, + id: filterValue?.account ?? '', type: 'ACCOUNT' }, { - id: filterValue?.assetClass, + id: filterValue?.assetClass ?? '', type: 'ASSET_CLASS' }, { - id: filterValue?.holding?.dataSource, + id: filterValue?.holding?.dataSource ?? '', type: 'DATA_SOURCE' }, { - id: filterValue?.holding?.symbol, + id: filterValue?.holding?.symbol ?? '', type: 'SYMBOL' }, { - id: filterValue?.tag, + id: filterValue?.tag ?? '', type: 'TAG' } ]); @@ -541,7 +540,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { this.filterTypes.map((type) => { return { type, - id: null + id: '' }; }) ); @@ -673,7 +672,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { dataSource, name, symbol, - assetSubClassString: translate(assetSubClass), + assetSubClassString: translate(assetSubClass ?? ''), mode: SearchMode.ASSET_PROFILE as const }; } @@ -705,7 +704,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { dataSource, name, symbol, - assetSubClassString: translate(assetSubClass), + assetSubClassString: translate(assetSubClass ?? ''), mode: SearchMode.HOLDING as const }; } @@ -718,7 +717,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { private searchQuickLinks(aSearchTerm: string): SearchResultItem[] { const searchTerm = aSearchTerm.toLowerCase(); - const allRoutes = Object.values(internalRoutes) + const allRoutes = Object.values(internalRoutes) .filter(({ excludeFromAssistant }) => { if (isFunction(excludeFromAssistant)) { return excludeFromAssistant(this.user); @@ -726,13 +725,13 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { return !excludeFromAssistant; }) - .reduce((acc, route) => { + .reduce((acc, route) => { acc.push(route); if (route.subRoutes) { acc.push(...Object.values(route.subRoutes)); } return acc; - }, [] as InternalRoute[]); + }, []); const fuse = new Fuse(allRoutes, { keys: ['title'], @@ -755,6 +754,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { const symbol = this.user?.settings?.['filters.symbol']; const selectedHolding = this.holdings.find((holding) => { return ( + !!(dataSource && symbol) && getAssetProfileIdentifier({ dataSource: holding.dataSource, symbol: holding.symbol diff --git a/libs/ui/src/lib/assistant/assistant.html b/libs/ui/src/lib/assistant/assistant.html index 307269262..79e2f31a1 100644 --- a/libs/ui/src/lib/assistant/assistant.html +++ b/libs/ui/src/lib/assistant/assistant.html @@ -186,7 +186,7 @@
    @@ -210,7 +210,7 @@ type="button" [disabled]=" !portfolioFilterForm.filterForm.dirty || - portfolioFilterForm.disabled + portfolioFilterForm.disabled() " (click)="onApplyFilters()" > diff --git a/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts b/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts index 2f4c18288..0c75b5954 100644 --- a/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts +++ b/libs/ui/src/lib/benchmark/benchmark-detail-dialog/benchmark-detail-dialog.component.ts @@ -12,18 +12,17 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, Inject, - OnDestroy, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { format } from 'date-fns'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; import { GfLineChartComponent } from '../../line-chart/line-chart.component'; import { GfValueComponent } from '../../value/value.component'; @@ -44,16 +43,15 @@ import { BenchmarkDetailDialogParams } from './interfaces/interfaces'; styleUrls: ['./benchmark-detail-dialog.component.scss'], templateUrl: 'benchmark-detail-dialog.html' }) -export class GfBenchmarkDetailDialogComponent implements OnDestroy, OnInit { +export class GfBenchmarkDetailDialogComponent implements OnInit { public assetProfile: AdminMarketDataDetails['assetProfile']; public historicalDataItems: LineChartItem[]; public value: number; - private unsubscribeSubject = new Subject(); - public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private destroyRef: DestroyRef, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: BenchmarkDetailDialogParams ) {} @@ -64,7 +62,7 @@ export class GfBenchmarkDetailDialogComponent implements OnDestroy, OnInit { dataSource: this.data.dataSource, symbol: this.data.symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ assetProfile, marketData }) => { this.assetProfile = assetProfile; @@ -88,9 +86,4 @@ export class GfBenchmarkDetailDialogComponent implements OnDestroy, OnInit { public onClose() { this.dialogRef.close(); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/libs/ui/src/lib/benchmark/benchmark-detail-dialog/interfaces/interfaces.ts b/libs/ui/src/lib/benchmark/benchmark-detail-dialog/interfaces/interfaces.ts index 291f4c973..b01403284 100644 --- a/libs/ui/src/lib/benchmark/benchmark-detail-dialog/interfaces/interfaces.ts +++ b/libs/ui/src/lib/benchmark/benchmark-detail-dialog/interfaces/interfaces.ts @@ -3,7 +3,7 @@ import { ColorScheme } from '@ghostfolio/common/types'; import { DataSource } from '@prisma/client'; export interface BenchmarkDetailDialogParams { - colorScheme: ColorScheme; + colorScheme?: ColorScheme; dataSource: DataSource; deviceType: string; locale: string; diff --git a/libs/ui/src/lib/benchmark/benchmark.component.html b/libs/ui/src/lib/benchmark/benchmark.component.html index ab6db8887..8820f2ec1 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.html +++ b/libs/ui/src/lib/benchmark/benchmark.component.html @@ -15,7 +15,7 @@
    {{ element?.name }}
    - @if (showSymbol) { + @if (showSymbol()) {
    {{ element?.symbol }}
    @@ -98,7 +98,7 @@ @if (element?.performances?.allTimeHigh?.date) { } @@ -123,7 +123,7 @@
    + -} @else if (benchmarks?.length === 0) { +} @else if (benchmarks()?.length === 0) {
    No data available
    diff --git a/libs/ui/src/lib/benchmark/benchmark.component.ts b/libs/ui/src/lib/benchmark/benchmark.component.ts index adef1f41b..5dbc4648b 100644 --- a/libs/ui/src/lib/benchmark/benchmark.component.ts +++ b/libs/ui/src/lib/benchmark/benchmark.component.ts @@ -16,13 +16,15 @@ import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, - EventEmitter, - Input, - OnChanges, - OnDestroy, - Output, - ViewChild + DestroyRef, + computed, + effect, + inject, + input, + output, + viewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; @@ -34,7 +36,6 @@ import { addIcons } from 'ionicons'; import { ellipsisHorizontal, trashOutline } from 'ionicons/icons'; import { isNumber } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject, takeUntil } from 'rxjs'; import { translate } from '../i18n'; import { GfTrendIndicatorComponent } from '../trend-indicator/trend-indicator.component'; @@ -61,41 +62,58 @@ import { BenchmarkDetailDialogParams } from './benchmark-detail-dialog/interface styleUrls: ['./benchmark.component.scss'], templateUrl: './benchmark.component.html' }) -export class GfBenchmarkComponent implements OnChanges, OnDestroy { - @Input() benchmarks: Benchmark[]; - @Input() deviceType: string; - @Input() hasPermissionToDeleteItem: boolean; - @Input() locale = getLocale(); - @Input() showSymbol = true; - @Input() user: User; - - @Output() itemDeleted = new EventEmitter(); - - @ViewChild(MatSort) sort: MatSort; - - public dataSource = new MatTableDataSource([]); - public displayedColumns = [ - 'name', - 'date', - 'change', - 'marketCondition', - 'actions' - ]; - public isLoading = true; - public isNumber = isNumber; - public resolveMarketCondition = resolveMarketCondition; - public translate = translate; - - private unsubscribeSubject = new Subject(); - - public constructor( - private dialog: MatDialog, - private notificationService: NotificationService, - private route: ActivatedRoute, - private router: Router - ) { +export class GfBenchmarkComponent { + public readonly benchmarks = input.required(); + public readonly deviceType = input.required(); + public readonly hasPermissionToDeleteItem = input(); + public readonly locale = input(getLocale()); + public readonly showSymbol = input(true); + public readonly user = input(); + + public readonly itemDeleted = output(); + + protected readonly sort = viewChild(MatSort); + + protected readonly dataSource = new MatTableDataSource([]); + protected readonly displayedColumns = computed(() => { + return [ + 'name', + ...(this.user()?.settings?.isExperimentalFeatures + ? ['trend50d', 'trend200d'] + : []), + 'date', + 'change', + 'marketCondition', + 'actions' + ]; + }); + protected isLoading = true; + protected readonly isNumber = isNumber; + protected readonly resolveMarketCondition = resolveMarketCondition; + protected readonly translate = translate; + + private readonly destroyRef = inject(DestroyRef); + private readonly dialog = inject(MatDialog); + private readonly notificationService = inject(NotificationService); + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + + public constructor() { + effect(() => { + const benchmarks = this.benchmarks(); + + if (benchmarks) { + this.dataSource.data = benchmarks; + this.dataSource.sortingDataAccessor = getLowercase; + + this.dataSource.sort = this.sort() ?? null; + + this.isLoading = false; + } + }); + this.route.queryParams - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { if ( params['benchmarkDetailDialog'] && @@ -112,30 +130,7 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy { addIcons({ ellipsisHorizontal, trashOutline }); } - public ngOnChanges() { - if (this.benchmarks) { - this.dataSource.data = this.benchmarks; - this.dataSource.sortingDataAccessor = getLowercase; - - this.dataSource.sort = this.sort; - - this.isLoading = false; - } - - if (this.user?.settings?.isExperimentalFeatures) { - this.displayedColumns = [ - 'name', - 'trend50d', - 'trend200d', - 'date', - 'change', - 'marketCondition', - 'actions' - ]; - } - } - - public onDeleteItem({ dataSource, symbol }: AssetProfileIdentifier) { + protected onDeleteItem({ dataSource, symbol }: AssetProfileIdentifier) { this.notificationService.confirm({ confirmFn: () => { this.itemDeleted.emit({ dataSource, symbol }); @@ -145,17 +140,15 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy { }); } - public onOpenBenchmarkDialog({ dataSource, symbol }: AssetProfileIdentifier) { + protected onOpenBenchmarkDialog({ + dataSource, + symbol + }: AssetProfileIdentifier) { this.router.navigate([], { queryParams: { dataSource, symbol, benchmarkDetailDialog: true } }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private openBenchmarkDetailDialog({ dataSource, symbol @@ -167,17 +160,17 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy { data: { dataSource, symbol, - colorScheme: this.user?.settings?.colorScheme, - deviceType: this.deviceType, - locale: this.locale + colorScheme: this.user()?.settings?.colorScheme, + deviceType: this.deviceType(), + locale: this.locale() }, - height: this.deviceType === 'mobile' ? '98vh' : undefined, - width: this.deviceType === 'mobile' ? '100vw' : '50rem' + height: this.deviceType() === 'mobile' ? '98vh' : undefined, + width: this.deviceType() === 'mobile' ? '100vw' : '50rem' }); dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.router.navigate(['.'], { relativeTo: this.route }); }); diff --git a/libs/ui/src/lib/currency-selector/currency-selector.component.ts b/libs/ui/src/lib/currency-selector/currency-selector.component.ts index 35a911716..7b6236fbb 100644 --- a/libs/ui/src/lib/currency-selector/currency-selector.component.ts +++ b/libs/ui/src/lib/currency-selector/currency-selector.component.ts @@ -4,13 +4,16 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, DoCheck, ElementRef, - Input, - OnDestroy, OnInit, - ViewChild + ViewChild, + inject, + input, + viewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl, FormGroupDirective, @@ -21,15 +24,14 @@ import { import { MatAutocomplete, MatAutocompleteModule, - MatAutocompleteSelectedEvent + MatOption } from '@angular/material/autocomplete'; import { MatFormFieldControl, MatFormFieldModule } from '@angular/material/form-field'; import { MatInput, MatInputModule } from '@angular/material/input'; -import { Subject } from 'rxjs'; -import { map, startWith, takeUntil } from 'rxjs/operators'; +import { map, startWith } from 'rxjs/operators'; import { AbstractMatFormField } from '../shared/abstract-mat-form-field'; @@ -58,21 +60,19 @@ import { AbstractMatFormField } from '../shared/abstract-mat-form-field'; templateUrl: 'currency-selector.component.html' }) export class GfCurrencySelectorComponent - extends AbstractMatFormField - implements DoCheck, OnDestroy, OnInit + extends AbstractMatFormField + implements DoCheck, OnInit { - @Input() private currencies: string[] = []; - @Input() private formControlName: string; - - @ViewChild(MatInput) private input: MatInput; - @ViewChild('currencyAutocomplete') public currencyAutocomplete: MatAutocomplete; - public control = new FormControl(); + public readonly control = new FormControl(null); + public readonly currencies = input.required(); public filteredCurrencies: string[] = []; + public readonly formControlName = input.required(); - private unsubscribeSubject = new Subject(); + private readonly destroyRef = inject(DestroyRef); + private readonly input = viewChild.required(MatInput); public constructor( public readonly _elementRef: ElementRef, @@ -86,6 +86,19 @@ export class GfCurrencySelectorComponent this.controlType = 'currency-selector'; } + public get empty() { + return this.input().empty; + } + + public set value(value: string | null) { + this.control.setValue(value); + super.value = value; + } + + public focus() { + this.input().focus(); + } + public ngOnInit() { if (this.disabled) { this.control.disable(); @@ -94,17 +107,18 @@ export class GfCurrencySelectorComponent const formGroup = this.formGroupDirective.form; if (formGroup) { - const control = formGroup.get(this.formControlName); + const control = formGroup.get(this.formControlName()); if (control) { - this.value = this.currencies.find((value) => { - return value === control.value; - }); + this.value = + this.currencies().find((value) => { + return value === control.value; + }) ?? null; } } this.control.valueChanges - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { if (super.value) { super.value = null; @@ -113,10 +127,10 @@ export class GfCurrencySelectorComponent this.control.valueChanges .pipe( - takeUntil(this.unsubscribeSubject), + takeUntilDestroyed(this.destroyRef), startWith(''), map((value) => { - return value ? this.filter(value) : this.currencies.slice(); + return value ? this.filter(value) : this.currencies().slice(); }) ) .subscribe((values) => { @@ -124,42 +138,22 @@ export class GfCurrencySelectorComponent }); } - public get empty() { - return this.input?.empty; - } - - public focus() { - this.input.focus(); - } - public ngDoCheck() { if (this.ngControl) { this.validateRequired(); - this.errorState = this.ngControl.invalid && this.ngControl.touched; + this.errorState = !!(this.ngControl.invalid && this.ngControl.touched); this.stateChanges.next(); } } - public onUpdateCurrency(event: MatAutocompleteSelectedEvent) { - super.value = event.option.value; - } - - public set value(value: string) { - this.control.setValue(value); - super.value = value; - } - - public ngOnDestroy() { - super.ngOnDestroy(); - - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); + public onUpdateCurrency({ option }: { option: MatOption }) { + super.value = option.value; } private filter(value: string) { - const filterValue = value?.toLowerCase(); + const filterValue = value.toLowerCase(); - return this.currencies.filter((currency) => { + return this.currencies().filter((currency) => { return currency.toLowerCase().startsWith(filterValue); }); } @@ -168,7 +162,7 @@ export class GfCurrencySelectorComponent const requiredCheck = super.required ? !super.value : false; if (requiredCheck) { - this.ngControl.control.setErrors({ invalidData: true }); + this.ngControl.control?.setErrors({ invalidData: true }); } } } diff --git a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts index 7461f6729..c8e281609 100644 --- a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts +++ b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts @@ -12,14 +12,15 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, ElementRef, - EventEmitter, Input, OnChanges, OnDestroy, - Output, - ViewChild + output, + viewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, FormControl, @@ -57,7 +58,7 @@ import { } from 'date-fns'; import { isNumber } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject, debounceTime, takeUntil } from 'rxjs'; +import { debounceTime } from 'rxjs'; import { FireCalculatorService } from './fire-calculator.service'; @@ -90,35 +91,40 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { @Input() retirementDate: Date; @Input() savingsRate = 0; - @Output() annualInterestRateChanged = new EventEmitter(); - @Output() calculationCompleted = - new EventEmitter(); - @Output() projectedTotalAmountChanged = new EventEmitter(); - @Output() retirementDateChanged = new EventEmitter(); - @Output() savingsRateChanged = new EventEmitter(); - - @ViewChild('chartCanvas') chartCanvas: ElementRef; - public calculatorForm = this.formBuilder.group({ - annualInterestRate: new FormControl(undefined), - paymentPerPeriod: new FormControl(undefined), - principalInvestmentAmount: new FormControl(undefined), - projectedTotalAmount: new FormControl(undefined), - retirementDate: new FormControl(undefined) + annualInterestRate: new FormControl(null), + paymentPerPeriod: new FormControl(null), + principalInvestmentAmount: new FormControl(null), + projectedTotalAmount: new FormControl(null), + retirementDate: new FormControl(null) }); + public chart: Chart<'bar'>; public isLoading = true; public minDate = addDays(new Date(), 1); public periodsToRetire = 0; + protected readonly annualInterestRateChanged = output(); + + protected readonly calculationCompleted = + output(); + + protected readonly projectedTotalAmountChanged = output(); + protected readonly retirementDateChanged = output(); + protected readonly savingsRateChanged = output(); + private readonly CONTRIBUTION_PERIOD = 12; + private readonly DEFAULT_RETIREMENT_DATE = startOfMonth( addYears(new Date(), 10) ); - private unsubscribeSubject = new Subject(); + + private readonly chartCanvas = + viewChild.required>('chartCanvas'); public constructor( private changeDetectorRef: ChangeDetectorRef, + private destroyRef: DestroyRef, private fireCalculatorService: FireCalculatorService, private formBuilder: FormBuilder ) { @@ -131,46 +137,68 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { ); this.calculatorForm.valueChanges - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.initialize(); }); this.calculatorForm.valueChanges - .pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + .pipe(debounceTime(500), takeUntilDestroyed(this.destroyRef)) .subscribe(() => { const { projectedTotalAmount, retirementDate } = this.calculatorForm.getRawValue(); - this.calculationCompleted.emit({ - projectedTotalAmount, - retirementDate - }); + if (projectedTotalAmount !== null && retirementDate !== null) { + this.calculationCompleted.emit({ + projectedTotalAmount, + retirementDate + }); + } }); this.calculatorForm .get('annualInterestRate') - .valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + ?.valueChanges.pipe( + debounceTime(500), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((annualInterestRate) => { - this.annualInterestRateChanged.emit(annualInterestRate); + if (annualInterestRate !== null) { + this.annualInterestRateChanged.emit(annualInterestRate); + } }); this.calculatorForm .get('paymentPerPeriod') - .valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + ?.valueChanges.pipe( + debounceTime(500), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((savingsRate) => { - this.savingsRateChanged.emit(savingsRate); + if (savingsRate !== null) { + this.savingsRateChanged.emit(savingsRate); + } }); this.calculatorForm .get('projectedTotalAmount') - .valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + ?.valueChanges.pipe( + debounceTime(500), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((projectedTotalAmount) => { - this.projectedTotalAmountChanged.emit(projectedTotalAmount); + if (projectedTotalAmount !== null) { + this.projectedTotalAmountChanged.emit(projectedTotalAmount); + } }); this.calculatorForm .get('retirementDate') - .valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + ?.valueChanges.pipe( + debounceTime(500), + takeUntilDestroyed(this.destroyRef) + ) .subscribe((retirementDate) => { - this.retirementDateChanged.emit(retirementDate); + if (retirementDate !== null) { + this.retirementDateChanged.emit(retirementDate); + } }); } @@ -196,11 +224,11 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { this.calculatorForm.patchValue( { annualInterestRate: - this.calculatorForm.get('annualInterestRate').value, + this.calculatorForm.get('annualInterestRate')?.value, paymentPerPeriod: this.getPMT(), principalInvestmentAmount: this.calculatorForm.get( 'principalInvestmentAmount' - ).value, + )?.value, projectedTotalAmount: Math.round(this.getProjectedTotalAmount()) || 0, retirementDate: @@ -210,7 +238,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { emitEvent: false } ); - this.calculatorForm.get('principalInvestmentAmount').disable(); + this.calculatorForm.get('principalInvestmentAmount')?.disable(); this.changeDetectorRef.markForCheck(); }); @@ -219,42 +247,43 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { if (this.hasPermissionToUpdateUserSettings === true) { this.calculatorForm .get('annualInterestRate') - .enable({ emitEvent: false }); - this.calculatorForm.get('paymentPerPeriod').enable({ emitEvent: false }); + ?.enable({ emitEvent: false }); + this.calculatorForm.get('paymentPerPeriod')?.enable({ emitEvent: false }); this.calculatorForm .get('projectedTotalAmount') - .enable({ emitEvent: false }); + ?.enable({ emitEvent: false }); } else { this.calculatorForm .get('annualInterestRate') - .disable({ emitEvent: false }); - this.calculatorForm.get('paymentPerPeriod').disable({ emitEvent: false }); + ?.disable({ emitEvent: false }); + this.calculatorForm + .get('paymentPerPeriod') + ?.disable({ emitEvent: false }); this.calculatorForm .get('projectedTotalAmount') - .disable({ emitEvent: false }); + ?.disable({ emitEvent: false }); } - this.calculatorForm.get('retirementDate').disable({ emitEvent: false }); + this.calculatorForm.get('retirementDate')?.disable({ emitEvent: false }); } public setMonthAndYear( normalizedMonthAndYear: Date, datepicker: MatDatepicker ) { - const retirementDate = this.calculatorForm.get('retirementDate').value; + const retirementDate = + this.calculatorForm.get('retirementDate')?.value ?? + this.DEFAULT_RETIREMENT_DATE; const newRetirementDate = setMonth( setYear(retirementDate, normalizedMonthAndYear.getFullYear()), normalizedMonthAndYear.getMonth() ); - this.calculatorForm.get('retirementDate').setValue(newRetirementDate); + this.calculatorForm.get('retirementDate')?.setValue(newRetirementDate); datepicker.close(); } public ngOnDestroy() { this.chart?.destroy(); - - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); } private initialize() { @@ -262,7 +291,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { const chartData = this.getChartData(); - if (this.chartCanvas) { + if (this.chartCanvas()) { if (this.chart) { this.chart.data.labels = chartData.labels; @@ -272,7 +301,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { this.chart.update(); } else { - this.chart = new Chart<'bar'>(this.chartCanvas.nativeElement, { + this.chart = new Chart<'bar'>(this.chartCanvas().nativeElement, { data: chartData, options: { plugins: { @@ -288,14 +317,12 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { return `Total: ${new Intl.NumberFormat(this.locale, { currency: this.currency, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore: Only supported from ES2020 or later currencyDisplay: 'code', style: 'currency' }).format(totalAmount)}`; }, label: (context) => { - let label = context.dataset.label || ''; + let label = context.dataset.label ?? ''; if (label) { label += ': '; @@ -426,12 +453,16 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { } private getPeriodsToRetire(): number { - if (this.calculatorForm.get('projectedTotalAmount').value) { + const projectedTotalAmount = this.calculatorForm.get( + 'projectedTotalAmount' + )?.value; + + if (projectedTotalAmount) { let periods = this.fireCalculatorService.calculatePeriodsToRetire({ P: this.getP(), PMT: this.getPMT(), r: this.getR(), - totalAmount: this.calculatorForm.get('projectedTotalAmount').value + totalAmount: projectedTotalAmount }); if (periods === Infinity) { @@ -453,12 +484,16 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { } private getPMT() { - return this.calculatorForm.get('paymentPerPeriod').value; + return this.calculatorForm.get('paymentPerPeriod')?.value ?? 0; } private getProjectedTotalAmount() { - if (this.calculatorForm.get('projectedTotalAmount').value) { - return this.calculatorForm.get('projectedTotalAmount').value; + const projectedTotalAmount = this.calculatorForm.get( + 'projectedTotalAmount' + )?.value; + + if (projectedTotalAmount) { + return projectedTotalAmount; } const { totalAmount } = @@ -473,12 +508,12 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { } private getR() { - return this.calculatorForm.get('annualInterestRate').value / 100; + return (this.calculatorForm.get('annualInterestRate')?.value ?? 0) / 100; } private getRetirementDate(): Date { if (this.periodsToRetire === Number.MAX_SAFE_INTEGER) { - return undefined; + return this.DEFAULT_RETIREMENT_DATE; } const monthsToRetire = this.periodsToRetire % 12; diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts index 7e7094dd3..1b7b99fa2 100644 --- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts @@ -5,10 +5,12 @@ import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, - Inject, - OnDestroy, - OnInit + DestroyRef, + OnInit, + inject, + signal } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; @@ -23,7 +25,7 @@ import { MatInputModule } from '@angular/material/input'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { calendarClearOutline, refreshOutline } from 'ionicons/icons'; -import { Subject, takeUntil } from 'rxjs'; +import { isNil } from 'lodash'; import { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces'; @@ -45,26 +47,27 @@ import { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces' styleUrls: ['./historical-market-data-editor-dialog.scss'], templateUrl: 'historical-market-data-editor-dialog.html' }) -export class GfHistoricalMarketDataEditorDialogComponent - implements OnDestroy, OnInit -{ - private unsubscribeSubject = new Subject(); +export class GfHistoricalMarketDataEditorDialogComponent implements OnInit { + public readonly data = + inject(MAT_DIALOG_DATA); + + protected readonly marketPrice = signal(this.data.marketPrice); + + private readonly destroyRef = inject(DestroyRef); + private readonly locale = + this.data.user.settings.locale ?? inject(MAT_DATE_LOCALE); public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, - @Inject(MAT_DIALOG_DATA) - public data: HistoricalMarketDataEditorDialogParams, private dataService: DataService, - private dateAdapter: DateAdapter, - public dialogRef: MatDialogRef, - @Inject(MAT_DATE_LOCALE) private locale: string + private dateAdapter: DateAdapter, + public dialogRef: MatDialogRef ) { addIcons({ calendarClearOutline, refreshOutline }); } public ngOnInit() { - this.locale = this.data.user?.settings?.locale; this.dateAdapter.setLocale(this.locale); } @@ -79,36 +82,37 @@ export class GfHistoricalMarketDataEditorDialogComponent dateString: this.data.dateString, symbol: this.data.symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ marketPrice }) => { - this.data.marketPrice = marketPrice; + this.marketPrice.set(marketPrice); this.changeDetectorRef.markForCheck(); }); } public onUpdate() { + const marketPrice = this.marketPrice(); + + if (isNil(marketPrice)) { + return; + } + this.dataService .postMarketData({ dataSource: this.data.dataSource, marketData: { marketData: [ { - date: this.data.dateString, - marketPrice: this.data.marketPrice + marketPrice, + date: this.data.dateString } ] }, symbol: this.data.symbol }) - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.dialogRef.close({ withRefresh: true }); }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html index 8e7e30649..7bb5827ef 100644 --- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html @@ -28,7 +28,8 @@ matInput name="marketPrice" type="number" - [(ngModel)]="data.marketPrice" + [ngModel]="marketPrice()" + (ngModelChange)="marketPrice.set($event)" /> {{ data.currency }} diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts index 4248b3fdb..edb9a852f 100644 --- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts @@ -6,7 +6,7 @@ export interface HistoricalMarketDataEditorDialogParams { currency: string; dataSource: DataSource; dateString: string; - marketPrice: number; + marketPrice?: number; symbol: string; user: User; } diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html index 3c2807146..91e3dd8d7 100644 --- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -3,28 +3,24 @@
    {{ itemByMonth.key }}
    - @for (dayItem of days; track dayItem; let i = $index) { + @for (day of days; track day) {
    diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.spec.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.spec.ts new file mode 100644 index 000000000..7cb9636f0 --- /dev/null +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.spec.ts @@ -0,0 +1,60 @@ +import { DataService } from '@ghostfolio/ui/services'; + +import { signal } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormBuilder } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { DeviceDetectorService } from 'ngx-device-detector'; + +import { GfHistoricalMarketDataEditorComponent } from './historical-market-data-editor.component'; + +jest.mock( + './historical-market-data-editor-dialog/historical-market-data-editor-dialog.component', + () => ({ + GfHistoricalMarketDataEditorDialogComponent: class {} + }) +); + +describe('GfHistoricalMarketDataEditorComponent', () => { + let component: GfHistoricalMarketDataEditorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [GfHistoricalMarketDataEditorComponent], + providers: [ + FormBuilder, + { provide: DataService, useValue: {} }, + { + provide: DeviceDetectorService, + useValue: { + deviceInfo: signal({ deviceType: 'desktop' }) + } + }, + { provide: MatDialog, useValue: {} }, + { provide: MatSnackBar, useValue: {} } + ] + }).compileComponents(); + + fixture = TestBed.createComponent(GfHistoricalMarketDataEditorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('formatDay', () => { + it('should pad single digit days with zero', () => { + expect(component.formatDay(1)).toBe('01'); + expect(component.formatDay(9)).toBe('09'); + }); + + it('should not pad double digit days', () => { + expect(component.formatDay(10)).toBe('10'); + expect(component.formatDay(31)).toBe('31'); + }); + }); +}); diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts index 098f4e295..cde180dd9 100644 --- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts @@ -8,16 +8,21 @@ import { LineChartItem, User } from '@ghostfolio/common/interfaces'; import { DataService } from '@ghostfolio/ui/services'; import { CommonModule } from '@angular/common'; +import type { HttpErrorResponse } from '@angular/common/http'; import { ChangeDetectionStrategy, Component, + computed, + DestroyRef, EventEmitter, + inject, + input, Input, OnChanges, - OnDestroy, OnInit, Output } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; @@ -40,7 +45,7 @@ import { first, last } from 'lodash'; import ms from 'ms'; import { DeviceDetectorService } from 'ngx-device-detector'; import { parse as csvToJson } from 'papaparse'; -import { EMPTY, Subject, takeUntil } from 'rxjs'; +import { EMPTY } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { GfHistoricalMarketDataEditorDialogComponent } from './historical-market-data-editor-dialog/historical-market-data-editor-dialog.component'; @@ -54,74 +59,80 @@ import { HistoricalMarketDataEditorDialogParams } from './historical-market-data templateUrl: './historical-market-data-editor.component.html' }) export class GfHistoricalMarketDataEditorComponent - implements OnChanges, OnDestroy, OnInit + implements OnChanges, OnInit { + private static readonly HISTORICAL_DATA_TEMPLATE = `date;marketPrice\n${format( + new Date(), + DATE_FORMAT + )};123.45`; + @Input() currency: string; @Input() dataSource: DataSource; @Input() dateOfFirstActivity: string; - @Input() locale = getLocale(); - @Input() marketData: MarketData[]; @Input() symbol: string; @Input() user: User; @Output() marketDataChanged = new EventEmitter(); - public days = Array(31); - public defaultDateFormat: string; - public deviceType: string; public historicalDataForm = this.formBuilder.group({ historicalData: this.formBuilder.group({ csvString: '' }) }); - public historicalDataItems: LineChartItem[]; public marketDataByMonth: { [yearMonth: string]: { - [day: string]: Pick & { day: number }; + [day: string]: { + date: Date; + day: number; + marketPrice?: number; + }; }; } = {}; - private static readonly HISTORICAL_DATA_TEMPLATE = `date;marketPrice\n${format( - new Date(), - DATE_FORMAT - )};123.45`; - - private unsubscribeSubject = new Subject(); + public readonly locale = input(getLocale()); + public readonly marketData = input.required(); + + protected readonly days = Array.from({ length: 31 }, (_, i) => i + 1); + protected readonly defaultDateFormat = computed(() => + getDateFormatString(this.locale()) + ); + + private readonly destroyRef = inject(DestroyRef); + private readonly deviceDetectorService = inject(DeviceDetectorService); + private readonly deviceType = computed( + () => this.deviceDetectorService.deviceInfo().deviceType + ); + private readonly historicalDataItems = computed(() => + this.marketData().map(({ date, marketPrice }) => { + return { + date: format(date, DATE_FORMAT), + value: marketPrice + }; + }) + ); public constructor( private dataService: DataService, - private deviceService: DeviceDetectorService, private dialog: MatDialog, private formBuilder: FormBuilder, private snackBar: MatSnackBar - ) { - this.deviceType = this.deviceService.getDeviceInfo().deviceType; - } + ) {} public ngOnInit() { this.initializeHistoricalDataForm(); } public ngOnChanges() { - this.defaultDateFormat = getDateFormatString(this.locale); - - this.historicalDataItems = this.marketData.map(({ date, marketPrice }) => { - return { - date: format(date, DATE_FORMAT), - value: marketPrice - }; - }); - if (this.dateOfFirstActivity) { let date = parseISO(this.dateOfFirstActivity); - const missingMarketData: Partial[] = []; + const missingMarketData: { date: Date; marketPrice?: number }[] = []; - if (this.historicalDataItems?.[0]?.date) { + if (this.historicalDataItems()?.[0]?.date) { while ( isBefore( date, - parse(this.historicalDataItems[0].date, DATE_FORMAT, new Date()) + parse(this.historicalDataItems()[0].date, DATE_FORMAT, new Date()) ) ) { missingMarketData.push({ @@ -133,9 +144,10 @@ export class GfHistoricalMarketDataEditorComponent } } - const marketDataItems = [...missingMarketData, ...this.marketData]; + const marketDataItems = [...missingMarketData, ...this.marketData()]; - if (!isToday(last(marketDataItems)?.date)) { + const lastDate = last(marketDataItems)?.date; + if (!lastDate || !isToday(lastDate)) { marketDataItems.push({ date: new Date() }); } @@ -160,25 +172,34 @@ export class GfHistoricalMarketDataEditorComponent // Fill up missing months const dates = Object.keys(this.marketDataByMonth).sort(); + const startDateString = first(dates); const startDate = min([ parseISO(this.dateOfFirstActivity), - parseISO(first(dates)) + ...(startDateString ? [parseISO(startDateString)] : []) ]); - const endDate = parseISO(last(dates)); + const endDateString = last(dates); - let currentDate = startDate; + if (endDateString) { + const endDate = parseISO(endDateString); - while (isBefore(currentDate, endDate)) { - const key = format(currentDate, 'yyyy-MM'); - if (!this.marketDataByMonth[key]) { - this.marketDataByMonth[key] = {}; - } + let currentDate = startDate; - currentDate = addMonths(currentDate, 1); + while (isBefore(currentDate, endDate)) { + const key = format(currentDate, 'yyyy-MM'); + if (!this.marketDataByMonth[key]) { + this.marketDataByMonth[key] = {}; + } + + currentDate = addMonths(currentDate, 1); + } } } } + public formatDay(day: number): string { + return day < 10 ? `0${day}` : `${day}`; + } + public isDateOfInterest(aDateString: string) { // Date is valid and in the past const date = parse(aDateString, DATE_FORMAT, new Date()); @@ -201,7 +222,8 @@ export class GfHistoricalMarketDataEditorComponent const dialogRef = this.dialog.open< GfHistoricalMarketDataEditorDialogComponent, - HistoricalMarketDataEditorDialogParams + HistoricalMarketDataEditorDialogParams, + { withRefresh: boolean } >(GfHistoricalMarketDataEditorDialogComponent, { data: { marketPrice, @@ -211,13 +233,13 @@ export class GfHistoricalMarketDataEditorComponent symbol: this.symbol, user: this.user }, - height: this.deviceType === 'mobile' ? '98vh' : '80vh', - width: this.deviceType === 'mobile' ? '100vw' : '50rem' + height: this.deviceType() === 'mobile' ? '98vh' : '80vh', + width: this.deviceType() === 'mobile' ? '100vw' : '50rem' }); dialogRef .afterClosed() - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(({ withRefresh } = { withRefresh: false }) => { this.marketDataChanged.emit(withRefresh); }); @@ -225,15 +247,15 @@ export class GfHistoricalMarketDataEditorComponent public onImportHistoricalData() { try { - const marketData = csvToJson( - this.historicalDataForm.controls['historicalData'].controls['csvString'] - .value, + const marketData = csvToJson( + this.historicalDataForm.controls.historicalData.controls.csvString + .value ?? '', { dynamicTyping: true, header: true, skipEmptyLines: true } - ).data as UpdateMarketDataDto[]; + ).data; this.dataService .postMarketData({ @@ -244,13 +266,13 @@ export class GfHistoricalMarketDataEditorComponent symbol: this.symbol }) .pipe( - catchError(({ error, message }) => { + catchError(({ error, message }: HttpErrorResponse) => { this.snackBar.open(`${error}: ${message[0]}`, undefined, { duration: ms('3 seconds') }); return EMPTY; }), - takeUntil(this.unsubscribeSubject) + takeUntilDestroyed(this.destroyRef) ) .subscribe(() => { this.initializeHistoricalDataForm(); @@ -268,11 +290,6 @@ export class GfHistoricalMarketDataEditorComponent } } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } - private initializeHistoricalDataForm() { this.historicalDataForm.setValue({ historicalData: { diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.html b/libs/ui/src/lib/holdings-table/holdings-table.component.html index 7c7f6b829..3bb387ae4 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.html +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.html @@ -52,7 +52,7 @@
    @@ -76,8 +76,8 @@
    @@ -100,8 +100,8 @@
    @@ -121,8 +121,8 @@
    @@ -142,9 +142,9 @@
    @@ -166,9 +166,9 @@ -
    + + -@if (isLoading) { +@if (isLoading()) { } -@if (dataSource.data.length > pageSize && !isLoading) { +@if (dataSource.data.length > pageSize() && !isLoading()) {
    diff --git a/libs/ui/src/lib/top-holdings/top-holdings.component.ts b/libs/ui/src/lib/top-holdings/top-holdings.component.ts index 75a96fc5c..7c9ae033f 100644 --- a/libs/ui/src/lib/top-holdings/top-holdings.component.ts +++ b/libs/ui/src/lib/top-holdings/top-holdings.component.ts @@ -20,15 +20,14 @@ import { EventEmitter, Input, OnChanges, - OnDestroy, Output, ViewChild } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; +import { capitalize } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject } from 'rxjs'; import { GfValueComponent } from '../value/value.component'; @@ -58,7 +57,7 @@ import { GfValueComponent } from '../value/value.component'; styleUrls: ['./top-holdings.component.scss'], templateUrl: './top-holdings.component.html' }) -export class GfTopHoldingsComponent implements OnChanges, OnDestroy { +export class GfTopHoldingsComponent implements OnChanges { @Input() baseCurrency: string; @Input() locale = getLocale(); @Input() pageSize = Number.MAX_SAFE_INTEGER; @@ -76,8 +75,6 @@ export class GfTopHoldingsComponent implements OnChanges, OnDestroy { ]; public isLoading = true; - private unsubscribeSubject = new Subject(); - public ngOnChanges() { this.isLoading = true; @@ -101,8 +98,23 @@ export class GfTopHoldingsComponent implements OnChanges, OnDestroy { }); } - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); + public prettifyAssetName(name: string) { + if (!name) { + return ''; + } + + return name + .split(' ') + .filter((token) => { + return !token.startsWith('(') && !token.endsWith(')'); + }) + .map((token) => { + if (token.length <= 2) { + return token.toUpperCase(); + } + + return capitalize(token); + }) + .join(' '); } } diff --git a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts index ce85c300e..7069cabb0 100644 --- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts +++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts @@ -25,7 +25,7 @@ import { } from '@angular/core'; import { DataSource } from '@prisma/client'; import { Big } from 'big.js'; -import type { TooltipOptions, ChartData } from 'chart.js'; +import type { ChartData, TooltipOptions } from 'chart.js'; import { LinearScale } from 'chart.js'; import { Chart, Tooltip } from 'chart.js'; import { TreemapController, TreemapElement } from 'chartjs-chart-treemap'; @@ -162,7 +162,9 @@ export class GfTreemapChartComponent private initialize() { this.isLoading = true; - const { endDate, startDate } = getIntervalFromDateRange(this.dateRange); + const { endDate, startDate } = getIntervalFromDateRange({ + dateRange: this.dateRange + }); const netPerformancePercentsWithCurrencyEffect = this.holdings.map( ({ dateOfFirstActivity, netPerformancePercentWithCurrencyEffect }) => { diff --git a/libs/ui/src/lib/value/value.component.html b/libs/ui/src/lib/value/value.component.html index 14080c16d..c4d6532a7 100644 --- a/libs/ui/src/lib/value/value.component.html +++ b/libs/ui/src/lib/value/value.component.html @@ -4,7 +4,26 @@ }
    - + + + + + + @if (enableCopyToClipboardButton) { + + } + + @if (value || value === 0 || value === null) {
    } + @if (!hasLabel) { + + }
    } @@ -88,6 +110,9 @@ @if (size === 'large') {
    + @if (hasLabel) { + + } @if (subLabel) { {{ subLabel }} } @@ -95,6 +120,9 @@ } @else { + @if (hasLabel) { + + } }
    diff --git a/libs/ui/src/lib/value/value.component.stories.ts b/libs/ui/src/lib/value/value.component.stories.ts index a1f9d06a0..214331efc 100644 --- a/libs/ui/src/lib/value/value.component.stories.ts +++ b/libs/ui/src/lib/value/value.component.stories.ts @@ -1,3 +1,4 @@ +import '@angular/localize/init'; import { moduleMetadata } from '@storybook/angular'; import type { Meta, StoryObj } from '@storybook/angular'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; @@ -16,6 +17,13 @@ export default { deviceType: { control: 'select', options: ['desktop', 'mobile'] + }, + enableCopyToClipboardButton: { + control: 'boolean' + }, + size: { + control: 'select', + options: ['small', 'medium', 'large'] } } } as Meta; @@ -51,7 +59,11 @@ export const Label: Story = { args: { locale: 'en-US', value: 7.25 - } + }, + render: (args) => ({ + props: args, + template: `Label` + }) }; export const PerformancePositive: Story = { diff --git a/libs/ui/src/lib/value/value.component.ts b/libs/ui/src/lib/value/value.component.ts index 795e16491..9c3330466 100644 --- a/libs/ui/src/lib/value/value.component.ts +++ b/libs/ui/src/lib/value/value.component.ts @@ -1,30 +1,41 @@ import { getLocale } from '@ghostfolio/common/helper'; +import { Clipboard } from '@angular/cdk/clipboard'; import { CommonModule } from '@angular/common'; import { - CUSTOM_ELEMENTS_SCHEMA, + AfterViewInit, ChangeDetectionStrategy, + ChangeDetectorRef, Component, + computed, + CUSTOM_ELEMENTS_SCHEMA, + ElementRef, + input, Input, OnChanges, - computed, - input + ViewChild } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSnackBar } from '@angular/material/snack-bar'; import { IonIcon } from '@ionic/angular/standalone'; +import { addIcons } from 'ionicons'; +import { copyOutline } from 'ionicons/icons'; import { isNumber } from 'lodash'; +import ms from 'ms'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, - imports: [CommonModule, IonIcon, NgxSkeletonLoaderModule], + imports: [CommonModule, IonIcon, MatButtonModule, NgxSkeletonLoaderModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-value', styleUrls: ['./value.component.scss'], templateUrl: './value.component.html' }) -export class GfValueComponent implements OnChanges { +export class GfValueComponent implements AfterViewInit, OnChanges { @Input() colorizeSign = false; @Input() deviceType: string; + @Input() enableCopyToClipboardButton = false; @Input() icon = ''; @Input() isAbsolute = false; @Input() isCurrency = false; @@ -37,12 +48,26 @@ export class GfValueComponent implements OnChanges { @Input() unit = ''; @Input() value: number | string = ''; + @ViewChild('labelContent', { static: false }) + labelContent!: ElementRef; + public absoluteValue = 0; public formattedValue = ''; + public hasLabel = false; public isNumber = false; public isString = false; public useAbsoluteValue = false; + public constructor( + private changeDetectorRef: ChangeDetectorRef, + private clipboard: Clipboard, + private snackBar: MatSnackBar + ) { + addIcons({ + copyOutline + }); + } + public readonly precision = input(); private readonly formatOptions = computed(() => { @@ -59,6 +84,17 @@ export class GfValueComponent implements OnChanges { return precision !== undefined && precision >= 0; } + public ngAfterViewInit() { + if (this.labelContent) { + const element = this.labelContent.nativeElement; + + this.hasLabel = + element.children.length > 0 || element.textContent.trim().length > 0; + + this.changeDetectorRef.markForCheck(); + } + } + public ngOnChanges() { this.initializeVariables(); @@ -137,6 +173,18 @@ export class GfValueComponent implements OnChanges { } } + public onCopyValueToClipboard() { + this.clipboard.copy(String(this.value)); + + this.snackBar.open( + '✅ ' + $localize`${this.value} has been copied to the clipboard`, + undefined, + { + duration: ms('3 seconds') + } + ); + } + private initializeVariables() { this.absoluteValue = 0; this.formattedValue = ''; diff --git a/libs/ui/src/lib/world-map-chart/world-map-chart.component.stories.ts b/libs/ui/src/lib/world-map-chart/world-map-chart.component.stories.ts index bdc983ae3..6d9d85295 100644 --- a/libs/ui/src/lib/world-map-chart/world-map-chart.component.stories.ts +++ b/libs/ui/src/lib/world-map-chart/world-map-chart.component.stories.ts @@ -44,7 +44,7 @@ export const Default: Story = { }) ), format: `{0} ${DEFAULT_CURRENCY}`, - isInPercent: false + isInPercentage: false } }; @@ -52,6 +52,6 @@ export const InPercentage: Story = { args: { countries: VWRL_COUNTRY_ALLOCATION, format: '{0}%', - isInPercent: true + isInPercentage: true } }; diff --git a/libs/ui/src/lib/world-map-chart/world-map-chart.component.ts b/libs/ui/src/lib/world-map-chart/world-map-chart.component.ts index 2a926cf7c..f86d4d010 100644 --- a/libs/ui/src/lib/world-map-chart/world-map-chart.component.ts +++ b/libs/ui/src/lib/world-map-chart/world-map-chart.component.ts @@ -21,7 +21,7 @@ import svgMap from 'svgmap'; export class GfWorldMapChartComponent implements OnChanges, OnDestroy { @Input() countries: { [code: string]: { name?: string; value: number } }; @Input() format: string; - @Input() isInPercent = false; + @Input() isInPercentage = false; @Input() locale = getLocale(); public isLoading = true; @@ -47,7 +47,7 @@ export class GfWorldMapChartComponent implements OnChanges, OnDestroy { } private initialize() { - if (this.isInPercent) { + if (this.isInPercentage) { // Convert value of countries to percentage let sum = 0; Object.keys(this.countries).map((country) => { diff --git a/libs/ui/tsconfig.json b/libs/ui/tsconfig.json index 04f4630bc..51348a52a 100644 --- a/libs/ui/tsconfig.json +++ b/libs/ui/tsconfig.json @@ -19,6 +19,7 @@ "target": "es2020", // TODO: Remove once solved in tsconfig.base.json "strict": false, + "strictNullChecks": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true }, diff --git a/package-lock.json b/package-lock.json index d415758b1..9afcf7af7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.237.0", + "version": "2.253.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.237.0", + "version": "2.253.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { @@ -21,26 +21,29 @@ "@angular/platform-browser-dynamic": "21.1.1", "@angular/router": "21.1.1", "@angular/service-worker": "21.1.1", + "@bull-board/api": "6.20.3", + "@bull-board/express": "6.20.3", + "@bull-board/nestjs": "6.20.3", "@codewithdan/observable-store": "2.2.15", "@date-fns/utc": "2.1.1", "@internationalized/number": "3.6.5", - "@ionic/angular": "8.7.8", + "@ionic/angular": "8.8.1", "@keyv/redis": "4.4.0", "@nestjs/bull": "11.0.4", - "@nestjs/cache-manager": "3.0.1", - "@nestjs/common": "11.1.8", - "@nestjs/config": "4.0.2", - "@nestjs/core": "11.1.8", + "@nestjs/cache-manager": "3.1.0", + "@nestjs/common": "11.1.14", + "@nestjs/config": "4.0.3", + "@nestjs/core": "11.1.14", "@nestjs/event-emitter": "3.0.1", - "@nestjs/jwt": "11.0.1", + "@nestjs/jwt": "11.0.2", "@nestjs/passport": "11.0.5", - "@nestjs/platform-express": "11.1.8", - "@nestjs/schedule": "6.0.1", + "@nestjs/platform-express": "11.1.14", + "@nestjs/schedule": "6.1.1", "@nestjs/serve-static": "5.0.4", "@openrouter/ai-sdk-provider": "0.7.2", - "@prisma/client": "6.19.0", - "@simplewebauthn/browser": "13.1.0", - "@simplewebauthn/server": "13.1.1", + "@prisma/client": "6.19.3", + "@simplewebauthn/browser": "13.2.2", + "@simplewebauthn/server": "13.2.2", "ai": "4.3.16", "alphavantage": "2.2.0", "big.js": "7.0.1", @@ -51,12 +54,13 @@ "chartjs-chart-treemap": "3.1.0", "chartjs-plugin-annotation": "3.1.0", "chartjs-plugin-datalabels": "2.2.0", - "cheerio": "1.0.0", + "cheerio": "1.2.0", "class-transformer": "0.5.1", - "class-validator": "0.14.3", + "class-validator": "0.15.1", "color": "5.0.3", + "cookie-parser": "1.4.7", "countries-and-timezones": "3.8.0", - "countries-list": "3.2.2", + "countries-list": "3.3.0", "countup.js": "2.9.0", "date-fns": "4.1.0", "dotenv": "17.2.3", @@ -68,14 +72,14 @@ "helmet": "7.0.0", "http-status-codes": "2.3.0", "ionicons": "8.0.13", - "jsonpath": "1.1.1", + "jsonpath": "1.2.1", "lodash": "4.17.23", - "marked": "17.0.1", + "marked": "17.0.2", "ms": "3.0.0-canary.1", - "ng-extract-i18n-merge": "3.2.1", + "ng-extract-i18n-merge": "3.3.0", "ngx-device-detector": "11.0.0", - "ngx-markdown": "21.0.1", - "ngx-skeleton-loader": "11.3.0", + "ngx-markdown": "21.1.0", + "ngx-skeleton-loader": "12.0.0", "open-color": "1.9.1", "papaparse": "5.3.1", "passport": "0.7.0", @@ -85,11 +89,11 @@ "passport-openidconnect": "0.1.2", "reflect-metadata": "0.2.2", "rxjs": "7.8.1", - "stripe": "20.3.0", - "svgmap": "2.14.0", + "stripe": "20.4.1", + "svgmap": "2.19.2", "tablemark": "4.1.0", "twitter-api-v2": "1.29.0", - "yahoo-finance2": "3.13.0", + "yahoo-finance2": "3.13.2", "zone.js": "0.16.0" }, "devDependencies": { @@ -107,22 +111,23 @@ "@eslint/eslintrc": "3.3.1", "@eslint/js": "9.35.0", "@nestjs/schematics": "11.0.9", - "@nestjs/testing": "11.1.8", - "@nx/angular": "22.4.5", - "@nx/eslint-plugin": "22.4.5", - "@nx/jest": "22.4.5", - "@nx/js": "22.4.5", - "@nx/module-federation": "22.4.5", - "@nx/nest": "22.4.5", - "@nx/node": "22.4.5", - "@nx/storybook": "22.4.5", - "@nx/web": "22.4.5", - "@nx/workspace": "22.4.5", + "@nestjs/testing": "11.1.14", + "@nx/angular": "22.5.3", + "@nx/eslint-plugin": "22.5.3", + "@nx/jest": "22.5.3", + "@nx/js": "22.5.3", + "@nx/module-federation": "22.5.3", + "@nx/nest": "22.5.3", + "@nx/node": "22.5.3", + "@nx/storybook": "22.5.3", + "@nx/web": "22.5.3", + "@nx/workspace": "22.5.3", "@schematics/angular": "21.1.1", "@storybook/addon-docs": "10.1.10", "@storybook/angular": "10.1.10", - "@trivago/prettier-plugin-sort-imports": "5.2.2", + "@trivago/prettier-plugin-sort-imports": "6.0.2", "@types/big.js": "6.2.2", + "@types/cookie-parser": "1.4.10", "@types/fast-redact": "3.0.4", "@types/google-spreadsheet": "3.1.5", "@types/jest": "30.0.0", @@ -130,7 +135,7 @@ "@types/lodash": "4.17.23", "@types/node": "22.15.17", "@types/papaparse": "5.3.7", - "@types/passport-google-oauth20": "2.0.16", + "@types/passport-google-oauth20": "2.0.17", "@types/passport-openidconnect": "0.1.3", "@typescript-eslint/eslint-plugin": "8.43.0", "@typescript-eslint/parser": "8.43.0", @@ -142,13 +147,13 @@ "jest": "30.2.0", "jest-environment-jsdom": "30.2.0", "jest-preset-angular": "16.0.0", - "nx": "22.4.5", + "nx": "22.5.3", "prettier": "3.8.1", "prettier-plugin-organize-attributes": "1.0.0", - "prisma": "6.19.0", + "prisma": "6.19.3", "react": "18.2.0", "react-dom": "18.2.0", - "replace-in-file": "8.3.0", + "replace-in-file": "8.4.0", "shx": "0.4.0", "storybook": "10.1.10", "ts-jest": "29.4.0", @@ -3617,6 +3622,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@borewit/text-codec": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.1.tgz", + "integrity": "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@braintree/sanitize-url": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", @@ -3631,6 +3646,53 @@ "devOptional": true, "license": "(Apache-2.0 AND BSD-3-Clause)" }, + "node_modules/@bull-board/api": { + "version": "6.20.3", + "resolved": "https://registry.npmjs.org/@bull-board/api/-/api-6.20.3.tgz", + "integrity": "sha512-cDrsJJsmF4DbbY8/5oHxO4qFtyFjxexsWQKHowsud/8H4mtZN7MZg4fCmNzfaxc9Ov7V6r9Y9F5G2Mq6t7ZEJg==", + "license": "MIT", + "dependencies": { + "redis-info": "^3.1.0" + }, + "peerDependencies": { + "@bull-board/ui": "6.20.3" + } + }, + "node_modules/@bull-board/express": { + "version": "6.20.3", + "resolved": "https://registry.npmjs.org/@bull-board/express/-/express-6.20.3.tgz", + "integrity": "sha512-S6BGeSf/PLwjx5W1IrKxoV8G6iiMmLqT/pldZ6BiC1IDldedisTtAdL1z117swXPv1H7/3hy0vr03dUr8bUCPg==", + "license": "MIT", + "dependencies": { + "@bull-board/api": "6.20.3", + "@bull-board/ui": "6.20.3", + "ejs": "^3.1.10", + "express": "^5.2.1" + } + }, + "node_modules/@bull-board/nestjs": { + "version": "6.20.3", + "resolved": "https://registry.npmjs.org/@bull-board/nestjs/-/nestjs-6.20.3.tgz", + "integrity": "sha512-VFi96Z2M8k3G26H1ivzQnpjKszxh90vrUm78VtMZH/sh8wjm88mJFDXcOgFutOaddx7cc9VNXlKsTTcu6okPFQ==", + "license": "MIT", + "peerDependencies": { + "@bull-board/api": "^6.20.3", + "@nestjs/bull-shared": "^10.0.0 || ^11.0.0", + "@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0", + "@nestjs/core": "^9.0.0 || ^10.0.0 || ^11.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0", + "rxjs": "^7.8.1" + } + }, + "node_modules/@bull-board/ui": { + "version": "6.20.3", + "resolved": "https://registry.npmjs.org/@bull-board/ui/-/ui-6.20.3.tgz", + "integrity": "sha512-oANyYoW0X+xd0j/09DRyh3u7Q3wqBtXiLEWyZUJIi/Bjp/hINwiw20RwWuRcaFkqkFylEJL9l+pjmeSA9X5L2A==", + "license": "MIT", + "dependencies": { + "@bull-board/api": "6.20.3" + } + }, "node_modules/@chevrotain/cst-dts-gen": { "version": "11.0.3", "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", @@ -5008,12 +5070,12 @@ } }, "node_modules/@ionic/angular": { - "version": "8.7.8", - "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-8.7.8.tgz", - "integrity": "sha512-IBN5h3nIOwbuglLit48S7wNeg7NHtl/vaKAHDggICyzI92cSg5yYL07Fz59pszhkBlZQUB5SQnml990Zj2bZUg==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-8.8.1.tgz", + "integrity": "sha512-Jp7LbouSHAnR00Dsa8qE1CSOZNqAfBCO0XKXScJNz8NKVoZe5fPGy/CboehGtAQ1xgzh2eDa15zMmyetXjAkYA==", "license": "MIT", "dependencies": { - "@ionic/core": "8.7.8", + "@ionic/core": "8.8.1", "ionicons": "^8.0.13", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" @@ -5027,14 +5089,17 @@ } }, "node_modules/@ionic/core": { - "version": "8.7.8", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.8.tgz", - "integrity": "sha512-GLWb/lz3kocpzTZTeQQ5xxoWz4CKHD6zpnbwJknTKsncebohAaw2KTe7uOw5toKQEDdohTseFuSGoDDBoRQ1Ug==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.1.tgz", + "integrity": "sha512-ksOUHyOEqoyUIVWcwCNSFZVGwNfP1DKrUVeN/Cdk/Xl9Rdd/5MLHGsrOQpWQfoCf3Csdnw+KHHPrXz/2fzMkMA==", "license": "MIT", "dependencies": { - "@stencil/core": "4.38.0", + "@stencil/core": "4.43.0", "ionicons": "^8.0.13", "tslib": "^2.1.0" + }, + "engines": { + "node": ">= 16" } }, "node_modules/@ioredis/commands": { @@ -6662,15 +6727,15 @@ } }, "node_modules/@module-federation/node": { - "version": "2.7.31", - "resolved": "https://registry.npmjs.org/@module-federation/node/-/node-2.7.31.tgz", - "integrity": "sha512-NSa0PFDKDLxmtfmCVHW9RhtfD9mcNOrp1d+cjVEoxb5x8dDI4jQTi1o3nsa9ettxs3bVtWhAUEQUNQBQ6ZA+Hw==", + "version": "2.7.32", + "resolved": "https://registry.npmjs.org/@module-federation/node/-/node-2.7.32.tgz", + "integrity": "sha512-hUj5v2GGwpNzl2gaJS4AyzCYRzJBhN8875A+ucKF9tq3jaQb5zpy3izYMISqqbN2q9a7jz3nEUgwAh3pjri+rQ==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/enhanced": "2.0.0", - "@module-federation/runtime": "2.0.0", - "@module-federation/sdk": "2.0.0", + "@module-federation/enhanced": "2.0.1", + "@module-federation/runtime": "2.0.1", + "@module-federation/sdk": "2.0.1", "btoa": "1.2.1", "encoding": "^0.1.13", "node-fetch": "2.7.0" @@ -6685,26 +6750,26 @@ } }, "node_modules/@module-federation/node/node_modules/@module-federation/bridge-react-webpack-plugin": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/bridge-react-webpack-plugin/-/bridge-react-webpack-plugin-2.0.0.tgz", - "integrity": "sha512-AVT/rZK6RHva6ZTYfsyQ8oP4xYNTws3OzqKW/YxWaLXwQ3oG9ZbF7fKl4jIKoMKuuy2L9MGVXS4CYPZy0s8fXg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/bridge-react-webpack-plugin/-/bridge-react-webpack-plugin-2.0.1.tgz", + "integrity": "sha512-D7LMW5EMAJShOMR1aZDAJ6s+MdsYDHaQyJADLQ3LaY0sne/BkVqkPikUwcO1IwOwKbXjYsDlQVOEvk9wZVRFhA==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/sdk": "2.0.0", + "@module-federation/sdk": "2.0.1", "@types/semver": "7.5.8", "semver": "7.6.3" } }, "node_modules/@module-federation/node/node_modules/@module-federation/cli": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/cli/-/cli-2.0.0.tgz", - "integrity": "sha512-IWGWbdgoeNcuA5jzqPr6pLTN1hovMQh9A1lgJp5fAvKfICfFXKq7K8nwMAQrWD6iEKApIenI0madk1Dg2PU3pw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/cli/-/cli-2.0.1.tgz", + "integrity": "sha512-2SL5Y8iODNX10y9T3CBLhHjSXo4afnA1BK82m4sNfZebuVO+o34bxewqwod9xfWq9xhTZmOSFZ+n+lgTKRv+CQ==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/dts-plugin": "2.0.0", - "@module-federation/sdk": "2.0.0", + "@module-federation/dts-plugin": "2.0.1", + "@module-federation/sdk": "2.0.1", "chalk": "3.0.0", "commander": "11.1.0", "jiti": "2.4.2" @@ -6717,14 +6782,14 @@ } }, "node_modules/@module-federation/node/node_modules/@module-federation/data-prefetch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/data-prefetch/-/data-prefetch-2.0.0.tgz", - "integrity": "sha512-KPyZoqNrb5WgFY2owYnMaO2Mg2DYD6KXLVI7GPguj7Z/4pPKEC+SUjWU2FuSfTeyE6ZIi0iFGdwerxzlQ6nfmw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/data-prefetch/-/data-prefetch-2.0.1.tgz", + "integrity": "sha512-Kq0P1OABGt6QAvs6TaE/zY9Ut9Y/oJFrzoSF3eWaCYbUAr2KD2SpTyMsPz4ssBzjeKXTgimugh6tHHd6mpCBIQ==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/runtime": "2.0.0", - "@module-federation/sdk": "2.0.0", + "@module-federation/runtime": "2.0.1", + "@module-federation/sdk": "2.0.1", "fs-extra": "9.1.0" }, "peerDependencies": { @@ -6741,16 +6806,16 @@ } }, "node_modules/@module-federation/node/node_modules/@module-federation/dts-plugin": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-2.0.0.tgz", - "integrity": "sha512-YyYMgLNARKdf3FLihnIzzUTgafHrqzR9YnKPmrfuCm2Jit+USqFT4QO58hcb0F5KSEyjB2ARPz9RM4XAVZhzMg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-2.0.1.tgz", + "integrity": "sha512-PLneTsf1fQS5/RTBedtLAAmCPRdMfIlhfJkOa8QH3WDJaQsqm8Wb3r2cTUBf2aNj/bP3aH/y6Hs9JFB/4x0l5g==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/error-codes": "2.0.0", - "@module-federation/managers": "2.0.0", - "@module-federation/sdk": "2.0.0", - "@module-federation/third-party-dts-extractor": "2.0.0", + "@module-federation/error-codes": "2.0.1", + "@module-federation/managers": "2.0.1", + "@module-federation/sdk": "2.0.1", + "@module-federation/third-party-dts-extractor": "2.0.1", "adm-zip": "^0.5.10", "ansi-colors": "^4.1.3", "axios": "^1.12.0", @@ -6775,23 +6840,23 @@ } }, "node_modules/@module-federation/node/node_modules/@module-federation/enhanced": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/enhanced/-/enhanced-2.0.0.tgz", - "integrity": "sha512-xeVrGvypYMvN8gJulbro3j1t8+aS1f9xjj4quwAAqgJF0Nz8bt7sXUYJyjUHPmC2UZsShZ0GnPHJNtI8/2GYjA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@module-federation/bridge-react-webpack-plugin": "2.0.0", - "@module-federation/cli": "2.0.0", - "@module-federation/data-prefetch": "2.0.0", - "@module-federation/dts-plugin": "2.0.0", - "@module-federation/error-codes": "2.0.0", - "@module-federation/inject-external-runtime-core-plugin": "2.0.0", - "@module-federation/managers": "2.0.0", - "@module-federation/manifest": "2.0.0", - "@module-federation/rspack": "2.0.0", - "@module-federation/runtime-tools": "2.0.0", - "@module-federation/sdk": "2.0.0", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/enhanced/-/enhanced-2.0.1.tgz", + "integrity": "sha512-EZIARQ/8ScoTP6PV8+E4SsmMYWK4ErrikZJ0G/FX8wvK8mCtdoKatFtvDN9++P6Nl78kN9zHYgAV4AHKdBVjfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/bridge-react-webpack-plugin": "2.0.1", + "@module-federation/cli": "2.0.1", + "@module-federation/data-prefetch": "2.0.1", + "@module-federation/dts-plugin": "2.0.1", + "@module-federation/error-codes": "2.0.1", + "@module-federation/inject-external-runtime-core-plugin": "2.0.1", + "@module-federation/managers": "2.0.1", + "@module-federation/manifest": "2.0.1", + "@module-federation/rspack": "2.0.1", + "@module-federation/runtime-tools": "2.0.1", + "@module-federation/sdk": "2.0.1", "btoa": "^1.2.1", "schema-utils": "^4.3.0", "upath": "2.0.1" @@ -6817,62 +6882,62 @@ } }, "node_modules/@module-federation/node/node_modules/@module-federation/error-codes": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-2.0.0.tgz", - "integrity": "sha512-9oE+hXuPv2zej7AxJ5hOgeRqlPs98meooV2FiutTfftLAyy2N6+Kwmmz5NR9d9t91weJj8N0cSHFoyenNHKTVg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-2.0.1.tgz", + "integrity": "sha512-2bJF/ft+qL9L6Zvq2t/G9/f/0wFL73cM8/NJ04uyYz9BjIgvx28K5qu8/6+IwgEEKATG7vOhBBVj6wH3S+5ASA==", "dev": true, "license": "MIT" }, "node_modules/@module-federation/node/node_modules/@module-federation/inject-external-runtime-core-plugin": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/inject-external-runtime-core-plugin/-/inject-external-runtime-core-plugin-2.0.0.tgz", - "integrity": "sha512-aZ6f4UU7KM5zBnHf3xsb2guqsfaEd6IlmuldbpED3JPk4ITwZk0DbvxRMr4prde7cfj8RH0nKMz2kmMncp+lIQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/inject-external-runtime-core-plugin/-/inject-external-runtime-core-plugin-2.0.1.tgz", + "integrity": "sha512-oAA7G+4GCHM+WRYfscR/x4GwCyM9CEqfdD9/x2L6y8mtLWK9anRLKTocsI759AvzXsbT1m3EQ5ki1O6wlwDu3g==", "dev": true, "license": "MIT", "peerDependencies": { - "@module-federation/runtime-tools": "2.0.0" + "@module-federation/runtime-tools": "2.0.1" } }, "node_modules/@module-federation/node/node_modules/@module-federation/managers": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/managers/-/managers-2.0.0.tgz", - "integrity": "sha512-ZmkRIujH+T3xvkmy04TNvviFH8xFOrNeKCLb4tlH4ifU/kLfjTu+PYO/KAEIsgtmrDnd52zTf22dg3ok85OAHA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/managers/-/managers-2.0.1.tgz", + "integrity": "sha512-KR01lSlcYRQ9C6hW2a8CQQtAE0LvfTLgtV/6ZNUTagw8sRfeDln+ggrZsYilKu9zl0i8RPDgpv/kS60o4lcxCQ==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/sdk": "2.0.0", + "@module-federation/sdk": "2.0.1", "find-pkg": "2.0.0", "fs-extra": "9.1.0" } }, "node_modules/@module-federation/node/node_modules/@module-federation/manifest": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/manifest/-/manifest-2.0.0.tgz", - "integrity": "sha512-AXwYyGiDJdfP9MteKyIdJrLwG5tp4qKaq0uOPiWHilYN3/21G0DM7f30HgJqgx3WSTFSh7hcq0a3V3EZHH/9TA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/manifest/-/manifest-2.0.1.tgz", + "integrity": "sha512-p8nYGjHWp17MsYdW/Vv0ogBDiTTsI1PHWPQbvVIqLQXDqwiesaRSRR1zziECXQoEL8lV5Bs+uSkcaJGhea9P+A==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/dts-plugin": "2.0.0", - "@module-federation/managers": "2.0.0", - "@module-federation/sdk": "2.0.0", + "@module-federation/dts-plugin": "2.0.1", + "@module-federation/managers": "2.0.1", + "@module-federation/sdk": "2.0.1", "chalk": "3.0.0", "find-pkg": "2.0.0" } }, "node_modules/@module-federation/node/node_modules/@module-federation/rspack": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/rspack/-/rspack-2.0.0.tgz", - "integrity": "sha512-1kziarKrPRM+rJax/AaMEZTwu7ORGed2xSxfdoP9GEbAFEGyNliadvw4kB6PqAfLad3PI4lQMX2vGMLI1KoyVQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/rspack/-/rspack-2.0.1.tgz", + "integrity": "sha512-SAlNE8iclFmzrKtx3/C2GivXYx6nPzx4MgQV01QG/a4LpnLbwlxzdZu3rqQ2swp4NNWT/t/GT7Y+7gfhyVa7mg==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/bridge-react-webpack-plugin": "2.0.0", - "@module-federation/dts-plugin": "2.0.0", - "@module-federation/inject-external-runtime-core-plugin": "2.0.0", - "@module-federation/managers": "2.0.0", - "@module-federation/manifest": "2.0.0", - "@module-federation/runtime-tools": "2.0.0", - "@module-federation/sdk": "2.0.0", + "@module-federation/bridge-react-webpack-plugin": "2.0.1", + "@module-federation/dts-plugin": "2.0.1", + "@module-federation/inject-external-runtime-core-plugin": "2.0.1", + "@module-federation/managers": "2.0.1", + "@module-federation/manifest": "2.0.1", + "@module-federation/runtime-tools": "2.0.1", + "@module-federation/sdk": "2.0.1", "btoa": "1.2.1" }, "peerDependencies": { @@ -6890,50 +6955,50 @@ } }, "node_modules/@module-federation/node/node_modules/@module-federation/runtime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-2.0.0.tgz", - "integrity": "sha512-vPxQrmQNq3Z1T+1fkHEvFwTdJq9wuCLvdp/lpu9k2Oy7QP/Pj6QoQ/S7J5MCIAoRwj8Wj3z3ma21/DyHwLGvzA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-2.0.1.tgz", + "integrity": "sha512-UQ72P5Oo40dS6vdhHetwTtIsbGciEr+bjoYvDgh1WLPfFlTYd8zo9cLfqaf3juuPfV3cMVARAVPmh16lQYpUGA==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/error-codes": "2.0.0", - "@module-federation/runtime-core": "2.0.0", - "@module-federation/sdk": "2.0.0" + "@module-federation/error-codes": "2.0.1", + "@module-federation/runtime-core": "2.0.1", + "@module-federation/sdk": "2.0.1" } }, "node_modules/@module-federation/node/node_modules/@module-federation/runtime-core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-2.0.0.tgz", - "integrity": "sha512-UhIGUs7Mg+TwMI2lgaLnj4UehpoyXbR7HDb2+vLikgBulPmFtodeWfsxCgENEwKsIY1vS0lOun15lNOn1vo3Xg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-2.0.1.tgz", + "integrity": "sha512-gOuCPSHoQGUGwlxfSTMInFX+QvLxdEWegGGMiLdU5vqbXuva4E9M+kXBBO7/0MkcBPMmVs0wOJGm0XOLeV2f1Q==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/error-codes": "2.0.0", - "@module-federation/sdk": "2.0.0" + "@module-federation/error-codes": "2.0.1", + "@module-federation/sdk": "2.0.1" } }, "node_modules/@module-federation/node/node_modules/@module-federation/runtime-tools": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-2.0.0.tgz", - "integrity": "sha512-eMDQN4hYpwvUnCNMjfQdtPVzYaO2DdauemHVc4HnyibgqijRzBwJh9bI2ph4R1xfYEm18+QmTrfXrRlaK2Xizw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-2.0.1.tgz", + "integrity": "sha512-AStdwBtsGB3jIfDg9oP+KyVPsimdaeHsP855gqCxDp1hi2+GKjlZWZx9ThkS8NytVSXSUysxqoUL1ivDoKgcCQ==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/runtime": "2.0.0", - "@module-federation/webpack-bundler-runtime": "2.0.0" + "@module-federation/runtime": "2.0.1", + "@module-federation/webpack-bundler-runtime": "2.0.1" } }, "node_modules/@module-federation/node/node_modules/@module-federation/sdk": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-2.0.0.tgz", - "integrity": "sha512-JYd1wTulsaoLT7HTk2oXL5y5797Z+H4mzxuUEKnSJo7R34RZSqehsqPSND7n0HT/1nf7uyn0Rb4qBfR3BVvdHQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-2.0.1.tgz", + "integrity": "sha512-32PwudojGjog51cwpTali7D6ud82oVgsyvOx9JjAzhvXBX96YI4mRsursuWcthDxmigJP9ZvUTXDuRUEDh1OQA==", "dev": true, "license": "MIT" }, "node_modules/@module-federation/node/node_modules/@module-federation/third-party-dts-extractor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-2.0.0.tgz", - "integrity": "sha512-B99+Wkbd2xIodVTjNCeFtFC89Uh2/AtYkSESlz4+6Cec42wyqrGxyfYm4qRY0LhJI+YmZXLk/RTm85m15eBKKg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-2.0.1.tgz", + "integrity": "sha512-neKSr6FNUeGRh+YR57l/QZUzPytJXuJx+babF7j5iGJG3FP+kfizr6QD0hgVis5KEoXMVbQ8yyvG0slERizeyw==", "dev": true, "license": "MIT", "dependencies": { @@ -6943,14 +7008,14 @@ } }, "node_modules/@module-federation/node/node_modules/@module-federation/webpack-bundler-runtime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-2.0.0.tgz", - "integrity": "sha512-XxiFR/A1G1fa9hTyylWNbs6yEU2hC7FqHAArFptD4U9qp/xyoLgqbK4M8LwltOAyAM8hRofcMdSyiRKVlWqAfQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-2.0.1.tgz", + "integrity": "sha512-u1NId3SF4lHDTmD2CHFEszulmXmIq1TGw9JYvnLx5rKJL7xt3aNxcb1GvkaYbRNVBXhSMjJ75E5LsQlZzyBx9A==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/runtime": "2.0.0", - "@module-federation/sdk": "2.0.0" + "@module-federation/runtime": "2.0.1", + "@module-federation/sdk": "2.0.1" } }, "node_modules/@module-federation/node/node_modules/jiti": { @@ -7531,9 +7596,9 @@ } }, "node_modules/@nestjs/cache-manager": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nestjs/cache-manager/-/cache-manager-3.0.1.tgz", - "integrity": "sha512-4UxTnR0fsmKL5YDalU2eLFVnL+OBebWUpX+hEduKGncrVKH4PPNoiRn1kXyOCjmzb0UvWgqubpssNouc8e0MCw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/cache-manager/-/cache-manager-3.1.0.tgz", + "integrity": "sha512-pEIqYZrBcE8UdkJmZRduurvoUfdU+3kRPeO1R2muiMbZnRuqlki5klFFNllO9LyYWzrx98bd1j0PSPKSJk1Wbw==", "license": "MIT", "peerDependencies": { "@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0", @@ -7544,12 +7609,12 @@ } }, "node_modules/@nestjs/common": { - "version": "11.1.8", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.8.tgz", - "integrity": "sha512-bbsOqwld/GdBfiRNc4nnjyWWENDEicq4SH+R5AuYatvf++vf1x5JIsHB1i1KtfZMD3eRte0D4K9WXuAYil6XAg==", + "version": "11.1.14", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.14.tgz", + "integrity": "sha512-IN/tlqd7Nl9gl6f0jsWEuOrQDaCI9vHzxv0fisHysfBQzfQIkqlv5A7w4Qge02BUQyczXT9HHPgHtWHCxhjRng==", "license": "MIT", "dependencies": { - "file-type": "21.0.0", + "file-type": "21.3.0", "iterare": "1.2.1", "load-esm": "1.0.3", "tslib": "2.8.1", @@ -7575,57 +7640,24 @@ } }, "node_modules/@nestjs/config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.2.tgz", - "integrity": "sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.3.tgz", + "integrity": "sha512-FQ3M3Ohqfl+nHAn5tp7++wUQw0f2nAk+SFKe8EpNRnIifPqvfJP6JQxPKtFLMOHbyer4X646prFG4zSRYEssQQ==", "license": "MIT", "dependencies": { - "dotenv": "16.4.7", - "dotenv-expand": "12.0.1", - "lodash": "4.17.21" + "dotenv": "17.2.3", + "dotenv-expand": "12.0.3", + "lodash": "4.17.23" }, "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", "rxjs": "^7.1.0" } }, - "node_modules/@nestjs/config/node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/@nestjs/config/node_modules/dotenv-expand": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.1.tgz", - "integrity": "sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==", - "license": "BSD-2-Clause", - "dependencies": { - "dotenv": "^16.4.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/@nestjs/config/node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, "node_modules/@nestjs/core": { - "version": "11.1.8", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.8.tgz", - "integrity": "sha512-7riWfmTmMhCJHZ5ZiaG+crj4t85IPCq/wLRuOUSigBYyFT2JZj0lVHtAdf4Davp9ouNI8GINBDt9h9b5Gz9nTw==", + "version": "11.1.14", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.14.tgz", + "integrity": "sha512-7OXPPMoDr6z+5NkoQKu4hOhfjz/YYqM3bNilPqv1WVFWrzSmuNXxvhbX69YMmNmRYascPXiwESqf5jJdjKXEww==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -7677,13 +7709,13 @@ } }, "node_modules/@nestjs/jwt": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.1.tgz", - "integrity": "sha512-HXSsc7SAnCnjA98TsZqrE7trGtHDnYXWp4Ffy6LwSmck1QvbGYdMzBquXofX5l6tIRpeY4Qidl2Ti2CVG77Pdw==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.2.tgz", + "integrity": "sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==", "license": "MIT", "dependencies": { "@types/jsonwebtoken": "9.0.10", - "jsonwebtoken": "9.0.2" + "jsonwebtoken": "9.0.3" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" @@ -7700,13 +7732,13 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "11.1.8", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.8.tgz", - "integrity": "sha512-rL6pZH9BW7BnL5X2eWbJMtt86uloAKjFgyY5+L2UkizgfEp7rgAs0+Z1z0BcW2Pgu5+q8O7RKPNyHJ/9ZNz/ZQ==", + "version": "11.1.14", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.14.tgz", + "integrity": "sha512-Fs+/j+mBSBSXErOQJ/YdUn/HqJGSJ4pGfiJyYOyz04l42uNVnqEakvu1kXLbxMabR6vd6/h9d6Bi4tso9p7o4Q==", "license": "MIT", "dependencies": { - "cors": "2.8.5", - "express": "5.1.0", + "cors": "2.8.6", + "express": "5.2.1", "multer": "2.0.2", "path-to-regexp": "8.3.0", "tslib": "2.8.1" @@ -7721,12 +7753,12 @@ } }, "node_modules/@nestjs/schedule": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.0.1.tgz", - "integrity": "sha512-v3yO6cSPAoBSSyH67HWnXHzuhPhSNZhRmLY38JvCt2sqY8sPMOODpcU1D79iUMFf7k16DaMEbL4Mgx61ZhiC8Q==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.1.1.tgz", + "integrity": "sha512-kQl1RRgi02GJ0uaUGCrXHCcwISsCsJDciCKe38ykJZgnAeeoeVWs8luWtBo4AqAAXm4nS5K8RlV0smHUJ4+2FA==", "license": "MIT", "dependencies": { - "cron": "4.3.3" + "cron": "4.4.0" }, "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", @@ -7910,9 +7942,9 @@ } }, "node_modules/@nestjs/testing": { - "version": "11.1.8", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.8.tgz", - "integrity": "sha512-E6K+0UTKztcPxJzLnQa7S34lFjZbrj3Z1r7c5y5WDrL1m5HD1H4AeyBhicHgdaFmxjLAva2bq0sYKy/S7cdeYA==", + "version": "11.1.14", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.14.tgz", + "integrity": "sha512-cQxX0ronsTbpfHz8/LYOVWXxoTxv6VoxrnuZoQaVX7QV2PSMqxWE7/9jSQR0GcqAFUEmFP34c6EJqfkjfX/k4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -8305,20 +8337,20 @@ } }, "node_modules/@nx/angular": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/angular/-/angular-22.4.5.tgz", - "integrity": "sha512-mwffAG7qhElwtWCEIaH7bTJuE3foaFBa3LWReqNc9HkIZmka0BDHRReg3wyhfSGq4ZQlYXK5sQS2uDJd+Qj97Q==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/angular/-/angular-22.5.3.tgz", + "integrity": "sha512-Z5vNcPl95CsTnRhDszWZ0ort22dEtMxdqJFkVzdNwolhfiLm1eKP2Rc8q9MnUdrZMeTqDKnCckG2qs12FZrIkw==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "22.4.5", - "@nx/eslint": "22.4.5", - "@nx/js": "22.4.5", - "@nx/module-federation": "22.4.5", - "@nx/rspack": "22.4.5", - "@nx/web": "22.4.5", - "@nx/webpack": "22.4.5", - "@nx/workspace": "22.4.5", + "@nx/devkit": "22.5.3", + "@nx/eslint": "22.5.3", + "@nx/js": "22.5.3", + "@nx/module-federation": "22.5.3", + "@nx/rspack": "22.5.3", + "@nx/web": "22.5.3", + "@nx/webpack": "22.5.3", + "@nx/workspace": "22.5.3", "@phenomnomnominal/tsquery": "~6.1.4", "@typescript-eslint/type-utils": "^8.0.0", "enquirer": "~2.3.6", @@ -8366,15 +8398,15 @@ } }, "node_modules/@nx/cypress": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/cypress/-/cypress-22.4.5.tgz", - "integrity": "sha512-FAGLQa7dnMW5Z93bS5isw9WgVNapCOgRFgxl9sA1ePstte3Vh0ajRpvVjVoeVHyA3qg6aQbE41ctErOdq/p5bg==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/cypress/-/cypress-22.5.3.tgz", + "integrity": "sha512-0jIYOhU1sFv5w2NYmyxIdpT4pC7QvUphrM4QLyUk8nEfO5gwfmON7JLNbtDveX+FeGpjy1zDjoBdd2OBzqUEcA==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "22.4.5", - "@nx/eslint": "22.4.5", - "@nx/js": "22.4.5", + "@nx/devkit": "22.5.3", + "@nx/eslint": "22.5.3", + "@nx/js": "22.5.3", "@phenomnomnominal/tsquery": "~6.1.4", "detect-port": "^1.5.1", "semver": "^7.6.3", @@ -8391,16 +8423,16 @@ } }, "node_modules/@nx/devkit": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-22.4.5.tgz", - "integrity": "sha512-mw5G6k/XTkL675eVIcFpyZdfdIc3wQMSSGWzfA6tQGmANDYc/NFGeZR9wDqXDceHXnYKoRO6g6GhKTOHUCW23Q==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-22.5.3.tgz", + "integrity": "sha512-zhRNTFsi4pbwg7L/zhBHtTOSevlgwm1iKlhPlQWoOv2PR6b+3JvjL8o4P1MbkIkut3Lsn+oTuJJ1LUPlr5vprg==", "dev": true, "license": "MIT", "dependencies": { "@zkochan/js-yaml": "0.0.7", "ejs": "^3.1.7", "enquirer": "~2.3.6", - "minimatch": "10.1.1", + "minimatch": "10.2.1", "semver": "^7.6.3", "tslib": "^2.3.0", "yargs-parser": "21.1.1" @@ -8409,14 +8441,37 @@ "nx": ">= 21 <= 23 || ^22.0.0-0" } }, + "node_modules/@nx/devkit/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@nx/devkit/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/@nx/devkit/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz", + "integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { "node": "20 || >=22" @@ -8426,33 +8481,33 @@ } }, "node_modules/@nx/docker": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/docker/-/docker-22.4.5.tgz", - "integrity": "sha512-ZgBjd/HCgqkulYJwUH+xQvgsoupVD+2leiFmK5lFjb6IDny/W1uB3EVL5BZxrz8ftMoqiq+AP6Ubiaj99V4hzQ==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/docker/-/docker-22.5.3.tgz", + "integrity": "sha512-4BO1hAyun2MM15w1oldUZtCvZEZlDXOCeEkimVij9znk6t4FUBnH7v87Uj4w5dhJAWB7yJFgQbF5w1fZbhRXfw==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "22.4.5", + "@nx/devkit": "22.5.3", "enquirer": "~2.3.6", "tslib": "^2.3.0" } }, "node_modules/@nx/eslint": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-22.4.5.tgz", - "integrity": "sha512-/N/kG86gqagDziC7Ij/WwAnjjXx55E1Jbpp3kkau3Ncj+wjPoLqCebpg6aW83VJQ7a4SUU0BO3U5bkqQZPGBXQ==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-22.5.3.tgz", + "integrity": "sha512-XJKpwnSJRCat+81sUDdJWWvKz3vKo/3X9oHMGDzTYx3mexCgKgpmHBuRVgnZ9n2IVDx8S5ye4ICc9qiY6oHWIA==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "22.4.5", - "@nx/js": "22.4.5", + "@nx/devkit": "22.5.3", + "@nx/js": "22.5.3", "semver": "^7.6.3", "tslib": "^2.3.0", "typescript": "~5.9.2" }, "peerDependencies": { "@zkochan/js-yaml": "0.0.7", - "eslint": "^8.0.0 || ^9.0.0" + "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { "@zkochan/js-yaml": { @@ -8461,14 +8516,14 @@ } }, "node_modules/@nx/eslint-plugin": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/eslint-plugin/-/eslint-plugin-22.4.5.tgz", - "integrity": "sha512-Kb3owVrbhRkJAjqEDsgDs8eSlI2/uEFOS35a8Z1drHIpMF6Zt9OHQf6bKELeXzG3fC2AGM3pyunauhbJ/ZmqMw==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/eslint-plugin/-/eslint-plugin-22.5.3.tgz", + "integrity": "sha512-dFz3nSUOV+VLc+ZQxWncKINhych6h5oEfInWp1+5WkeUBW5/x77IsM3Hpq1JrjAK6dqXjmzTsFnoU8c5Cf1Q4w==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "22.4.5", - "@nx/js": "22.4.5", + "@nx/devkit": "22.5.3", + "@nx/js": "22.5.3", "@phenomnomnominal/tsquery": "~6.1.4", "@typescript-eslint/type-utils": "^8.0.0", "@typescript-eslint/utils": "^8.0.0", @@ -8520,22 +8575,22 @@ } }, "node_modules/@nx/jest": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-22.4.5.tgz", - "integrity": "sha512-qlEJc0Jbp8E14g7+piHH8DXsAm6C3w1CLuvtE57+LFMhM2zbBDiQ8oeXBdFPEHLCfpbSK/4yCSEmkUj1Yyrs2A==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-22.5.3.tgz", + "integrity": "sha512-4yaGlApTR09zdz4fC4Ep0aENcaon5rDRDOUnEJblU67ik35jds9mczHq2rBMJO4Cnjj1pM9acm08Vb0Wg+9cuQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/reporters": "^30.0.2", "@jest/test-result": "^30.0.2", - "@nx/devkit": "22.4.5", - "@nx/js": "22.4.5", + "@nx/devkit": "22.5.3", + "@nx/js": "22.5.3", "@phenomnomnominal/tsquery": "~6.1.4", "identity-obj-proxy": "3.0.0", "jest-config": "^30.0.2", "jest-resolve": "^30.0.2", "jest-util": "^30.0.2", - "minimatch": "10.1.1", + "minimatch": "10.2.1", "picocolors": "^1.1.0", "resolve.exports": "2.0.3", "semver": "^7.6.3", @@ -8543,14 +8598,37 @@ "yargs-parser": "21.1.1" } }, + "node_modules/@nx/jest/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@nx/jest/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/@nx/jest/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz", + "integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { "node": "20 || >=22" @@ -8560,9 +8638,9 @@ } }, "node_modules/@nx/js": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/js/-/js-22.4.5.tgz", - "integrity": "sha512-t8972z2uF6X5i4FFmTlnvSwwxfHkk87zBpKQK0yMH5CzOENViVFNbiPnbvCIJcGNrgVUSALL3f2ngwKcTZObmA==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/js/-/js-22.5.3.tgz", + "integrity": "sha512-gglQYL6GeSH0mt6NpEFTXMFFFePU3B7TEyZq7LLUYZDH5y65izgNpdSAuEqYR7xHLtahVnesDlhPw3rtRiwMwA==", "dev": true, "license": "MIT", "dependencies": { @@ -8573,8 +8651,8 @@ "@babel/preset-env": "^7.23.2", "@babel/preset-typescript": "^7.22.5", "@babel/runtime": "^7.22.6", - "@nx/devkit": "22.4.5", - "@nx/workspace": "22.4.5", + "@nx/devkit": "22.5.3", + "@nx/workspace": "22.5.3", "@zkochan/js-yaml": "0.0.7", "babel-plugin-const-enum": "^1.0.1", "babel-plugin-macros": "^3.1.0", @@ -8648,18 +8726,18 @@ } }, "node_modules/@nx/module-federation": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/module-federation/-/module-federation-22.4.5.tgz", - "integrity": "sha512-aNO595Xk0B4av9tpAaePF0jjDooAiXN34xEpFleSCmf8y31371JfkI8WMSnIZLa5ehyk1U+oMxHyYtt7v0RFWw==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/module-federation/-/module-federation-22.5.3.tgz", + "integrity": "sha512-dKRkT/ULV+nXr7O25YMDwQu/4nxl27AcHJfOmVBVKquXrtrBu/d6dbypfFDMyr5pXqR5gb2euEw0mGVMyHLTiA==", "dev": true, "license": "MIT", "dependencies": { "@module-federation/enhanced": "^0.21.2", "@module-federation/node": "^2.7.21", "@module-federation/sdk": "^0.21.2", - "@nx/devkit": "22.4.5", - "@nx/js": "22.4.5", - "@nx/web": "22.4.5", + "@nx/devkit": "22.5.3", + "@nx/js": "22.5.3", + "@nx/web": "22.5.3", "@rspack/core": "1.6.8", "express": "^4.21.2", "http-proxy-middleware": "^3.0.5", @@ -8970,41 +9048,41 @@ } }, "node_modules/@nx/nest": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/nest/-/nest-22.4.5.tgz", - "integrity": "sha512-cFufm3cPuy7Cj10D8BB2Y+Vo1w/1ihQGeduXprC0gs719dI5zvyG8bVOYJ+m87HHdFVQ8ckIVVifO6T7ujWgFw==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/nest/-/nest-22.5.3.tgz", + "integrity": "sha512-DShcD4HdaABg9L7/qgLVgZj0LLSaaYa7Rrj8zquMGGZXrxozCTKzn/7vWrN7Am9hY4wEwaXTXwauHAFoIj9C9A==", "dev": true, "license": "MIT", "dependencies": { "@nestjs/schematics": "^11.0.0", - "@nx/devkit": "22.4.5", - "@nx/eslint": "22.4.5", - "@nx/js": "22.4.5", - "@nx/node": "22.4.5", + "@nx/devkit": "22.5.3", + "@nx/eslint": "22.5.3", + "@nx/js": "22.5.3", + "@nx/node": "22.5.3", "tslib": "^2.3.0" } }, "node_modules/@nx/node": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/node/-/node-22.4.5.tgz", - "integrity": "sha512-ZYN3uIeUs0jKPX9Io75DkISMo5ha15djVLPNFhsh6qgQkL7+mqXGeW3QiEso16XZqbl0Iw2Ye5msrBO6UShFkQ==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/node/-/node-22.5.3.tgz", + "integrity": "sha512-szdpsyRUzXBhMIiEk+zI3XPP/e0U1wNmIzkvgCS1e+ejsA1jq0EF5sppkdwJEQVAb6O5hiCPKED+1sSo4TR4/A==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "22.4.5", - "@nx/docker": "22.4.5", - "@nx/eslint": "22.4.5", - "@nx/jest": "22.4.5", - "@nx/js": "22.4.5", + "@nx/devkit": "22.5.3", + "@nx/docker": "22.5.3", + "@nx/eslint": "22.5.3", + "@nx/jest": "22.5.3", + "@nx/js": "22.5.3", "kill-port": "^1.6.1", "tcp-port-used": "^1.0.2", "tslib": "^2.3.0" } }, "node_modules/@nx/nx-darwin-arm64": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-22.4.5.tgz", - "integrity": "sha512-zdRHZv1AMvzgp+5g2VZNXXuqk0/n1wOFksOeZ6BRyKg6hC2YkjGyn5xle/UK668MDAwe9KKm4jizvztK/LlPuA==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-22.5.3.tgz", + "integrity": "sha512-cKXBq5bJanXp8uv6+wPvx/G4q4oFpOxMSPGaeFOVhbul2QHGGq+XMcSo+D8aYJCsk1YnbyAnnQ8r8RH/kTK5Mw==", "cpu": [ "arm64" ], @@ -9016,9 +9094,9 @@ ] }, "node_modules/@nx/nx-darwin-x64": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-22.4.5.tgz", - "integrity": "sha512-1NVWaSgpa8yawi2UILX4NE9UcMuNzAAGh95JSV2yJovRfKxFQgQSB6hj0gpJu+TLLVCroTqy4woSQ2a0SPodeQ==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-22.5.3.tgz", + "integrity": "sha512-mToS41o8I+8CfxYVRMTISkgT7I1cnazgwMf7U9DoLqKOwOZzj9WD3NmsWc1h69QNJPltbeRPS8y/wnhu7RHzRA==", "cpu": [ "x64" ], @@ -9030,9 +9108,9 @@ ] }, "node_modules/@nx/nx-freebsd-x64": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-22.4.5.tgz", - "integrity": "sha512-baaLz53wr/HsVfSJ7ZgIFCPAb/OtP7yPPasb3eIu65oVhSswGfgvz9+YINhuInUgW7x7STmRnhGeR8pj6iqFqw==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-22.5.3.tgz", + "integrity": "sha512-CAWysdFSZVbTfdjNXojd9TgXbZiK9i0k3njROeV+jORsDWw4Eth3PDmK94Wk916b3n2hS0UjyI6RZaMy2GEqzA==", "cpu": [ "x64" ], @@ -9044,9 +9122,9 @@ ] }, "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-22.4.5.tgz", - "integrity": "sha512-wRBPv/l39tz+sQjZUH4hygCsd/DoUXUbDYkR6lnNXWHAVyPUh48/27JozM8hD3o/G3O2Vd8PFQasIXtvy2GS0Q==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-22.5.3.tgz", + "integrity": "sha512-PRjPrijQQbdrvYwNuA3xQ3VXEQ4zfhnPjy+S2ZlQZqhFI4mlP22xfhOH1bQ7pIfzCNC2f/J9UMNYOrq/bEFjBg==", "cpu": [ "arm" ], @@ -9058,9 +9136,9 @@ ] }, "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-22.4.5.tgz", - "integrity": "sha512-6B/yCFiqjvV2Bkz6MKUtfFWjwtiF53DN07K1BFksMpQef+h2yE1IrGaG/OCl6VaVl4VRzQgLOluqP96M1yhDgg==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-22.5.3.tgz", + "integrity": "sha512-dmDBio/5z4Zch2VlRMdgBPm53d8xwq1l7xLj1dFMKjfE7ByfPukjPM7ZEYBiPckfiQfJBRh6HKDN7uEkA/y8CQ==", "cpu": [ "arm64" ], @@ -9072,9 +9150,9 @@ ] }, "node_modules/@nx/nx-linux-arm64-musl": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-22.4.5.tgz", - "integrity": "sha512-n0v60vRYn7BDHWB588snPZntLO2XC8/pvLd+QunneM2VGEPf51n5llX5U3AwTt/ybaZHWhbuHv0sJBIbT4I0GA==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-22.5.3.tgz", + "integrity": "sha512-E81ET/MnnKfuLhKiovF5ueJirHOMjhC1eK0MDM2Do9wdPyusZzfGSVFQ9DOHtg7L37dAE95NNd1lCVO8gJ96vg==", "cpu": [ "arm64" ], @@ -9086,9 +9164,9 @@ ] }, "node_modules/@nx/nx-linux-x64-gnu": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-22.4.5.tgz", - "integrity": "sha512-zT7nb1PRE3NcW/HFnbgKJ9ZPtCOeVDpbJ5J4ZhHj36ZAUWZVXFEIPq9VTIZFy5+0pioLUIClQQY7OUfwnV/Zig==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-22.5.3.tgz", + "integrity": "sha512-AgXCsPCzC0sAu2VRclMjs7LrvPQfqS3sFiehlXWTbNHQitPZLuAmQGb2l4T8lbMOs0Xn3EIrg6BF6/ntTTp6Xg==", "cpu": [ "x64" ], @@ -9100,9 +9178,9 @@ ] }, "node_modules/@nx/nx-linux-x64-musl": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-22.4.5.tgz", - "integrity": "sha512-r8Rls5BS7lGQbUNX1Z1S370XrOacOU1bQ/dxY8i7qahFQKnMwpFo0W8odhgzjk+vrC/WLf9jOgz5/JPzehQBIw==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-22.5.3.tgz", + "integrity": "sha512-sKs4bFQRu8Btxf5rMYKPsRVNxkQ2ey8sqoCyhJj8fwJF05DayK2ErJAR/rhtBK0c1NV7kQiKJA8nWBV3jnCdsg==", "cpu": [ "x64" ], @@ -9114,9 +9192,9 @@ ] }, "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-22.4.5.tgz", - "integrity": "sha512-Lv81LTnG6sSvBOq2vDSeyfzpF9X0cTGlJdzJOJzPZXCZGFhTV1ig9TdLiij/GM2JwV4Kvq5Co6YzA5dxtGUphQ==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-22.5.3.tgz", + "integrity": "sha512-KOCQLakSO5vl4D6et9qPytOAmkgq2IIuhI8A/g0xbD1LqrIlRPa+bdkZqOGpODYAk3NyKAk7hWHsqfXKHwwX6w==", "cpu": [ "arm64" ], @@ -9128,9 +9206,9 @@ ] }, "node_modules/@nx/nx-win32-x64-msvc": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-22.4.5.tgz", - "integrity": "sha512-52RfBcq9PXt76soCAZAJcNmCYrdsg6BvhBmjf0IFTMZ8IaeqZ9ktxAy1TZf/gCkOaM3ly4htbYMStiZ4MHX7Eg==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-22.5.3.tgz", + "integrity": "sha512-a6ZB2La82RIHcz4nrt3H6RZaOa+xkC2IPzhU9hMo2gbkLdIxn8wyof8uGA0frncmIVHuLc3nFAhpBOgf4j6tMA==", "cpu": [ "x64" ], @@ -9142,16 +9220,16 @@ ] }, "node_modules/@nx/rspack": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/rspack/-/rspack-22.4.5.tgz", - "integrity": "sha512-pqaJ713Jv82abDGisArEtKprAO0DuGxp7zddwpYW04J4Y8YmRAQFA3KriMPqjWTXuV3l4kpaqU7FtZ/3Xn1ShA==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/rspack/-/rspack-22.5.3.tgz", + "integrity": "sha512-2T5dkoC08FJGF8ZMiJDaKN6giXAljV0+LK7q5GJpSEUm+wtjZ/DRVoWSnlf8Dj/e0/cLb2GMElVcfjyDDDeV9w==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "22.4.5", - "@nx/js": "22.4.5", - "@nx/module-federation": "22.4.5", - "@nx/web": "22.4.5", + "@nx/devkit": "22.5.3", + "@nx/js": "22.5.3", + "@nx/module-federation": "22.5.3", + "@nx/web": "22.5.3", "@phenomnomnominal/tsquery": "~6.1.4", "@rspack/core": "1.6.8", "@rspack/dev-server": "^1.1.4", @@ -9573,16 +9651,16 @@ } }, "node_modules/@nx/storybook": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/storybook/-/storybook-22.4.5.tgz", - "integrity": "sha512-cxJDYpfpYcK0iuiJMHk6InLXXNLedj8VlOkRtcnZKuwDlC8quMSOuHKrdvBOjeOLV4C390/94BlzkToUZSey6g==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/storybook/-/storybook-22.5.3.tgz", + "integrity": "sha512-eP7cpRKnC1oYlOjSZU07Kf8BV8hNRPPXRib7BVdwwIUZ2v4K/b/cje2WCG729q4gk8fyoV8o7JNVgM+QGT8kBQ==", "dev": true, "license": "MIT", "dependencies": { - "@nx/cypress": "22.4.5", - "@nx/devkit": "22.4.5", - "@nx/eslint": "22.4.5", - "@nx/js": "22.4.5", + "@nx/cypress": "22.5.3", + "@nx/devkit": "22.5.3", + "@nx/eslint": "22.5.3", + "@nx/js": "22.5.3", "@phenomnomnominal/tsquery": "~6.1.4", "semver": "^7.6.3", "tslib": "^2.3.0" @@ -9592,14 +9670,14 @@ } }, "node_modules/@nx/web": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/web/-/web-22.4.5.tgz", - "integrity": "sha512-VXXkONZS7DEDDKUE8EUCiV7XhC+HmotExPKznU6NquoFpBZqvWCfC0rt/gKk2uIxJGu8qoISqtIIHFc6iO65RA==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/web/-/web-22.5.3.tgz", + "integrity": "sha512-Z7FYN5e9HIJAuJV0MU8jHaoEv9vgiLbpe1bbWPItfzIy02kWtSiS/aGZcLa34LDuWBfBaJVHZqFVp7OOPU26ew==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "22.4.5", - "@nx/js": "22.4.5", + "@nx/devkit": "22.5.3", + "@nx/js": "22.5.3", "detect-port": "^1.5.1", "http-server": "^14.1.0", "picocolors": "^1.1.0", @@ -9607,15 +9685,15 @@ } }, "node_modules/@nx/webpack": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-22.4.5.tgz", - "integrity": "sha512-3NZnJwkP1ztPc4Inz0g04rWf78P3U2np/kg3nKNf2I6kowWpcJakQCsWLufBzP48ooUtE3iPDVQoFIo3SgWqDg==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-22.5.3.tgz", + "integrity": "sha512-fEWvmynjxAfdyCH00Z3oaEedv/wKZAdHl8kz7UEiOJ7eKdGbbJIK0RuobXC1r2e2ERZ35vDrOiPYdruyfi35Jg==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.23.2", - "@nx/devkit": "22.4.5", - "@nx/js": "22.4.5", + "@nx/devkit": "22.5.3", + "@nx/js": "22.5.3", "@phenomnomnominal/tsquery": "~6.1.4", "ajv": "^8.12.0", "autoprefixer": "^10.4.9", @@ -9824,17 +9902,17 @@ } }, "node_modules/@nx/workspace": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-22.4.5.tgz", - "integrity": "sha512-QGapABrqBnRpEWbnd5UpbVCBzsYD+RlC1lWShXPpCM+dosR3qkGb+pSmxeSCsKbNVtCwYyyuRW+PvlF5Q5sU9A==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-22.5.3.tgz", + "integrity": "sha512-pioGwlt5zKB9PhX36I5KAeSml19Mq+g2KyQ9mh3F+3Lvft2JM4nIMELBaUfwPicPAOwNmrsx806IXO67Q4UHxQ==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "22.4.5", + "@nx/devkit": "22.5.3", "@zkochan/js-yaml": "0.0.7", "chalk": "^4.1.0", "enquirer": "~2.3.6", - "nx": "22.4.5", + "nx": "22.5.3", "picomatch": "4.0.2", "semver": "^7.6.3", "tslib": "^2.3.0", @@ -10220,34 +10298,101 @@ "tslib": "^2.8.1" } }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz", + "integrity": "sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz", + "integrity": "sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, "node_modules/@peculiar/asn1-ecc": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.4.0.tgz", - "integrity": "sha512-fJiYUBCJBDkjh347zZe5H81BdJ0+OGIg0X9z06v8xXUoql3MFeENUX0JsjCaVaU9A0L85PefLPGYkIoGpTnXLQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz", + "integrity": "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==", "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.4.0", - "@peculiar/asn1-x509": "^2.4.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz", + "integrity": "sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-rsa": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz", + "integrity": "sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz", + "integrity": "sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pfx": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-rsa": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.4.0.tgz", - "integrity": "sha512-6PP75voaEnOSlWR9sD25iCQyLgFZHXbmxvUfnnDcfL6Zh5h2iHW38+bve4LfH7a60x7fkhZZNmiYqAlAff9Img==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz", + "integrity": "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==", "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.4.0", - "@peculiar/asn1-x509": "^2.4.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-schema": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.4.0.tgz", - "integrity": "sha512-umbembjIWOrPSOzEGG5vxFLkeM8kzIhLkgigtsOrfLKnuzxWxejAcUX+q/SoZCdemlODOcr5WiYa7+dIEzBXZQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", "license": "MIT", "dependencies": { "asn1js": "^3.0.6", @@ -10256,17 +10401,51 @@ } }, "node_modules/@peculiar/asn1-x509": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.4.0.tgz", - "integrity": "sha512-F7mIZY2Eao2TaoVqigGMLv+NDdpwuBKU1fucHPONfzaBS4JXXCNCmfO0Z3dsy7JzKGqtDcYC1mr9JjaZQZNiuw==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz", + "integrity": "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==", "license": "MIT", "dependencies": { - "@peculiar/asn1-schema": "^2.4.0", + "@peculiar/asn1-schema": "^2.6.0", "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz", + "integrity": "sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", + "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@phenomnomnominal/tsquery": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/@phenomnomnominal/tsquery/-/tsquery-6.1.4.tgz", @@ -10313,9 +10492,9 @@ "license": "MIT" }, "node_modules/@prisma/client": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.0.tgz", - "integrity": "sha512-QXFT+N/bva/QI2qoXmjBzL7D6aliPffIwP+81AdTGq0FXDoLxLkWivGMawG8iM5B9BKfxLIXxfWWAF6wbuJU6g==", + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.3.tgz", + "integrity": "sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { @@ -10335,66 +10514,66 @@ } }, "node_modules/@prisma/config": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.0.tgz", - "integrity": "sha512-zwCayme+NzI/WfrvFEtkFhhOaZb/hI+X8TTjzjJ252VbPxAl2hWHK5NMczmnG9sXck2lsXrxIZuK524E25UNmg==", + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.3.tgz", + "integrity": "sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ==", "devOptional": true, "license": "Apache-2.0", "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", - "effect": "3.18.4", + "effect": "3.21.0", "empathic": "2.0.0" } }, "node_modules/@prisma/debug": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.0.tgz", - "integrity": "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==", + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.3.tgz", + "integrity": "sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.0.tgz", - "integrity": "sha512-pMRJ+1S6NVdXoB8QJAPIGpKZevFjxhKt0paCkRDTZiczKb7F4yTgRP8M4JdVkpQwmaD4EoJf6qA+p61godDokw==", + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.3.tgz", + "integrity": "sha512-RSYxtlYFl5pJ8ZePgMv0lZ9IzVCOdTPOegrs2qcbAEFrBI1G33h6wyC9kjQvo0DnYEhEVY0X4LsuFHXLKQk88g==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.19.0", - "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", - "@prisma/fetch-engine": "6.19.0", - "@prisma/get-platform": "6.19.0" + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/fetch-engine": "6.19.3", + "@prisma/get-platform": "6.19.3" } }, "node_modules/@prisma/engines-version": { - "version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773.tgz", - "integrity": "sha512-gV7uOBQfAFlWDvPJdQxMT1aSRur3a0EkU/6cfbAC5isV67tKDWUrPauyaHNpB+wN1ebM4A9jn/f4gH+3iHSYSQ==", + "version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7.tgz", + "integrity": "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.0.tgz", - "integrity": "sha512-OOx2Lda0DGrZ1rodADT06ZGqHzr7HY7LNMaFE2Vp8dp146uJld58sRuasdX0OiwpHgl8SqDTUKHNUyzEq7pDdQ==", + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.3.tgz", + "integrity": "sha512-tKtl/qco9Nt7LU5iKhpultD8O4vMCZcU2CHjNTnRrL1QvSUr5W/GcyFPjNL87GtRrwBc7ubXXD9xy4EvLvt8JA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.19.0", - "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", - "@prisma/get-platform": "6.19.0" + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/get-platform": "6.19.3" } }, "node_modules/@prisma/get-platform": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.0.tgz", - "integrity": "sha512-ym85WDO2yDhC3fIXHWYpG3kVMBA49cL1XD2GCsCF8xbwoy2OkDQY44gEbAt2X46IQ4Apq9H6g0Ex1iFfPqEkHA==", + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.3.tgz", + "integrity": "sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.19.0" + "@prisma/debug": "6.19.3" } }, "node_modules/@redis/client": { @@ -11573,9 +11752,9 @@ "license": "MIT" }, "node_modules/@rspack/plugin-react-refresh": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.0.tgz", - "integrity": "sha512-OO53gkrte/Ty4iRXxxM6lkwPGxsSsupFKdrPFnjwFIYrPvFLjeolAl5cTx+FzO5hYygJiGnw7iEKTmD+ptxqDA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.6.1.tgz", + "integrity": "sha512-eqqW5645VG3CzGzFgNg5HqNdHVXY+567PGjtDhhrM8t67caxmsSzRmT5qfoEIfBcGgFkH9vEg7kzXwmCYQdQDw==", "dev": true, "license": "MIT", "dependencies": { @@ -11696,15 +11875,15 @@ } }, "node_modules/@simplewebauthn/browser": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.1.0.tgz", - "integrity": "sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==", + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.2.2.tgz", + "integrity": "sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==", "license": "MIT" }, "node_modules/@simplewebauthn/server": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-13.1.1.tgz", - "integrity": "sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==", + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-13.2.2.tgz", + "integrity": "sha512-HcWLW28yTMGXpwE9VLx9J+N2KEUaELadLrkPEEI9tpI5la70xNEVEsu/C+m3u7uoq4FulLqZQhgBCzR9IZhFpA==", "license": "MIT", "dependencies": { "@hexagon/base64": "^1.1.27", @@ -11713,7 +11892,8 @@ "@peculiar/asn1-ecc": "^2.3.8", "@peculiar/asn1-rsa": "^2.3.8", "@peculiar/asn1-schema": "^2.3.8", - "@peculiar/asn1-x509": "^2.3.8" + "@peculiar/asn1-x509": "^2.3.8", + "@peculiar/x509": "^1.13.0" }, "engines": { "node": ">=20.0.0" @@ -11753,9 +11933,9 @@ "license": "MIT" }, "node_modules/@stencil/core": { - "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.38.0.tgz", - "integrity": "sha512-oC3QFKO0X1yXVvETgc8OLY525MNKhn9vISBrbtKnGoPlokJ6rI8Vk1RK22TevnNrHLI4SExNLbcDnqilKR35JQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.43.0.tgz", + "integrity": "sha512-6Uj2Z3lzLuufYAE7asZ6NLKgSwsB9uxl84Eh34PASnUjfj32GkrP4DtKK7fNeh1WFGGyffsTDka3gwtl+4reUg==", "license": "MIT", "bin": { "stencil": "bin/stencil" @@ -12306,14 +12486,13 @@ } }, "node_modules/@tokenizer/inflate": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", - "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", "license": "MIT", "dependencies": { - "debug": "^4.4.0", - "fflate": "^0.8.2", - "token-types": "^6.0.0" + "debug": "^4.4.3", + "token-types": "^6.1.1" }, "engines": { "node": ">=18" @@ -12330,25 +12509,28 @@ "license": "MIT" }, "node_modules/@trivago/prettier-plugin-sort-imports": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.2.tgz", - "integrity": "sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-6.0.2.tgz", + "integrity": "sha512-3DgfkukFyC/sE/VuYjaUUWoFfuVjPK55vOFDsxD56XXynFMCZDYFogH2l/hDfOsQAm1myoU/1xByJ3tWqtulXA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@babel/generator": "^7.26.5", - "@babel/parser": "^7.26.7", - "@babel/traverse": "^7.26.7", - "@babel/types": "^7.26.7", + "@babel/generator": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", "javascript-natural-sort": "^0.7.1", - "lodash": "^4.17.21" + "lodash-es": "^4.17.21", + "minimatch": "^9.0.0", + "parse-imports-exports": "^0.2.4" }, "engines": { - "node": ">18.12" + "node": ">= 20" }, "peerDependencies": { "@vue/compiler-sfc": "3.x", "prettier": "2.x - 3.x", + "prettier-plugin-ember-template-tag": ">= 2.0.0", "prettier-plugin-svelte": "3.x", "svelte": "4.x || 5.x" }, @@ -12356,6 +12538,9 @@ "@vue/compiler-sfc": { "optional": true }, + "prettier-plugin-ember-template-tag": { + "optional": true + }, "prettier-plugin-svelte": { "optional": true }, @@ -12364,6 +12549,32 @@ } } }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -12564,6 +12775,16 @@ "@types/node": "*" } }, + "node_modules/@types/cookie-parser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", + "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/express": "*" + } + }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -13180,9 +13401,9 @@ } }, "node_modules/@types/passport-google-oauth20": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.16.tgz", - "integrity": "sha512-ayXK2CJ7uVieqhYOc6k/pIr5pcQxOLB6kBev+QUGS7oEZeTgIs1odDobXRqgfBPvXzl0wXCQHftV5220czZCPA==", + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.17.tgz", + "integrity": "sha512-MHNOd2l7gOTCn3iS+wInPQMiukliAUvMpODO3VlXxOiwNEMSyzV7UNvAdqxSN872o8OXx1SqPDVT6tLW74AtqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14855,7 +15076,6 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, "license": "MIT" }, "node_modules/async-function": { @@ -14939,14 +15159,14 @@ } }, "node_modules/axios": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", - "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -15249,7 +15469,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -15503,7 +15722,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -16101,25 +16319,25 @@ } }, "node_modules/cheerio": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", - "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", "license": "MIT", "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "encoding-sniffer": "^0.2.0", - "htmlparser2": "^9.1.0", - "parse5": "^7.1.2", - "parse5-htmlparser2-tree-adapter": "^7.0.0", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", - "undici": "^6.19.5", + "undici": "^7.19.0", "whatwg-mimetype": "^4.0.0" }, "engines": { - "node": ">=18.17" + "node": ">=20.18.1" }, "funding": { "url": "https://github.com/cheeriojs/cheerio?sponsor=1" @@ -16142,25 +16360,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/cheerio/node_modules/htmlparser2": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", - "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "entities": "^4.5.0" - } - }, "node_modules/cheerio/node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -16295,14 +16494,14 @@ "license": "MIT" }, "node_modules/class-validator": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz", - "integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.15.1.tgz", + "integrity": "sha512-LqoS80HBBSCVhz/3KloUly0ovokxpdOLR++Al3J3+dHXWt9sTKlKd4eYtoxhxyUjoe5+UcIM+5k9MIxyBWnRTw==", "license": "MIT", "dependencies": { "@types/validator": "^13.15.3", "libphonenumber-js": "^1.11.1", - "validator": "^13.15.20" + "validator": "^13.15.22" } }, "node_modules/clean-css": { @@ -16747,13 +16946,12 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", "devOptional": true, "license": "MIT" }, @@ -16819,6 +17017,25 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, "node_modules/cookie-signature": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", @@ -16901,9 +17118,9 @@ "license": "MIT" }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -16911,6 +17128,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/corser": { @@ -16971,9 +17192,9 @@ } }, "node_modules/countries-list": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/countries-list/-/countries-list-3.2.2.tgz", - "integrity": "sha512-ABJ/RWQBrPWy+hRuZoW+0ooK8p65Eo3WmUZwHm6v4wmfSPznNAKzjy3+UUYrJK2v3182BVsgWxdB6ROidj39kw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/countries-list/-/countries-list-3.3.0.tgz", + "integrity": "sha512-XRUjS+dcZuNh/fg3+mka3bXgcg4TbQZ1gaK5IJqO6qulerBANl1bmrd20P2dgmPkBpP+5FnejiSF1gd7bgAg+g==", "license": "MIT" }, "node_modules/countup.js": { @@ -16990,9 +17211,9 @@ "license": "MIT" }, "node_modules/cron": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/cron/-/cron-4.3.3.tgz", - "integrity": "sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-4.4.0.tgz", + "integrity": "sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==", "license": "MIT", "dependencies": { "@types/luxon": "~3.7.0", @@ -17000,6 +17221,10 @@ }, "engines": { "node": ">=18.x" + }, + "funding": { + "type": "ko-fi", + "url": "https://ko-fi.com/intcreator" } }, "node_modules/cron-parser": { @@ -18207,6 +18432,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -18322,9 +18548,9 @@ } }, "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.6.tgz", + "integrity": "sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug==", "devOptional": true, "license": "MIT" }, @@ -18684,9 +18910,9 @@ "license": "MIT" }, "node_modules/effect": { - "version": "3.18.4", - "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", - "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.21.0.tgz", + "integrity": "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==", "devOptional": true, "license": "MIT", "dependencies": { @@ -18698,7 +18924,6 @@ "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, "license": "Apache-2.0", "dependencies": { "jake": "^10.8.5" @@ -19167,6 +19392,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/eslint": { "version": "9.35.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.35.0.tgz", @@ -19600,7 +19856,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -19769,18 +20024,19 @@ "license": "Apache-2.0" }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -19848,9 +20104,9 @@ } }, "node_modules/exsolve": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", - "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", "devOptional": true, "license": "MIT" }, @@ -19947,6 +20203,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, "license": "MIT" }, "node_modules/fast-redact": { @@ -20046,12 +20303,6 @@ "filenamify-url": "2.1.2" } }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "license": "MIT" - }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -20092,14 +20343,14 @@ } }, "node_modules/file-type": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.0.0.tgz", - "integrity": "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==", + "version": "21.3.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.0.tgz", + "integrity": "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==", "license": "MIT", "dependencies": { - "@tokenizer/inflate": "^0.2.7", - "strtok3": "^10.2.2", - "token-types": "^6.0.0", + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" }, "engines": { @@ -20113,7 +20364,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, "license": "Apache-2.0", "dependencies": { "minimatch": "^5.0.1" @@ -20123,7 +20373,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -20133,7 +20382,6 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -20332,9 +20580,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -20420,9 +20668,9 @@ } }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -21345,7 +21593,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -21656,9 +21903,9 @@ } }, "node_modules/htmlparser2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", - "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -21670,14 +21917,14 @@ "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", - "domutils": "^3.2.1", - "entities": "^6.0.0" + "domutils": "^3.2.2", + "entities": "^7.0.1" } }, "node_modules/htmlparser2/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -22985,7 +23232,6 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "dev": true, "license": "Apache-2.0", "dependencies": { "async": "^3.2.3", @@ -23004,7 +23250,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -24486,20 +24731,20 @@ "license": "MIT" }, "node_modules/jsonpath": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", - "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.2.1.tgz", + "integrity": "sha512-Jl6Jhk0jG+kP3yk59SSeGq7LFPR4JQz1DU0K+kXTysUhMostbhU3qh5mjTuf0PqFcXpAT7kvmMt9WxV10NyIgQ==", "license": "MIT", "dependencies": { - "esprima": "1.2.2", - "static-eval": "2.0.2", - "underscore": "1.12.1" + "esprima": "1.2.5", + "static-eval": "2.1.1", + "underscore": "1.13.6" } }, "node_modules/jsonpath/node_modules/esprima": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", - "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz", + "integrity": "sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -24509,12 +24754,12 @@ } }, "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", "license": "MIT", "dependencies": { - "jws": "^3.2.2", + "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -24530,27 +24775,6 @@ "npm": ">=6" } }, - "node_modules/jsonwebtoken/node_modules/jwa": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", - "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jsonwebtoken/node_modules/jws": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", - "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", - "license": "MIT", - "dependencies": { - "jwa": "^1.4.2", - "safe-buffer": "^5.0.1" - } - }, "node_modules/jsonwebtoken/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -25102,8 +25326,8 @@ "version": "4.17.22", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", - "license": "MIT", - "optional": true + "devOptional": true, + "license": "MIT" }, "node_modules/lodash.clonedeepwith": { "version": "4.5.0", @@ -25542,9 +25766,9 @@ } }, "node_modules/marked": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", - "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.2.tgz", + "integrity": "sha512-s5HZGFQea7Huv5zZcAGhJLT3qLpAfnY7v7GWkICUr0+Wd5TFEtdlRR2XUL5Gg+RH7u2Df595ifrxR03mBaw7gA==", "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -25797,7 +26021,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -25816,11 +26039,11 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -26193,9 +26416,9 @@ "license": "MIT" }, "node_modules/ng-extract-i18n-merge": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ng-extract-i18n-merge/-/ng-extract-i18n-merge-3.2.1.tgz", - "integrity": "sha512-Yq8uEBa32/Imlo+vnyY6rk+h0VOjWQT8r4Vgiw/YlnK0AzIXFxr6H/Ji3gTJKVsuRY6Tt1swBgmnkAUeDmklRw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ng-extract-i18n-merge/-/ng-extract-i18n-merge-3.3.0.tgz", + "integrity": "sha512-9VCi2gMSjvlz5+bvJ9wTzHEeEiVCM0lb/uvGx2K3FavG4p0HKhg0Y9Tjr/Hr23DSHAQQXS0gssIvznWW3DHIXQ==", "license": "MIT", "dependencies": { "@angular-devkit/architect": ">=0.2000.0 <0.2200.0", @@ -26225,16 +26448,16 @@ } }, "node_modules/ngx-markdown": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/ngx-markdown/-/ngx-markdown-21.0.1.tgz", - "integrity": "sha512-TQnxrU9b+JclgXBFVg0Xp/6YEMom+hpiEjBMlE56cIWzONNh7pshMeMkz972wWzvQvTP+55/BmEZQc+4Vq1MWg==", + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/ngx-markdown/-/ngx-markdown-21.1.0.tgz", + "integrity": "sha512-qiyn9Je20F9yS4/q0p1Xhk2b/HW0rHWWlJNRm8DIzJKNck9Rmn/BfFxq0webmQHPPyYkg2AjNq/ZeSqDTQJbsQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "optionalDependencies": { "clipboard": "^2.0.11", - "emoji-toolkit": ">= 8.0.0 < 10.0.0", + "emoji-toolkit": ">= 8.0.0 < 11.0.0", "katex": "^0.16.0", "mermaid": ">= 10.6.0 < 12.0.0", "prismjs": "^1.30.0" @@ -26243,15 +26466,15 @@ "@angular/common": "^21.0.0", "@angular/core": "^21.0.0", "@angular/platform-browser": "^21.0.0", - "marked": "^17.0.0 || ^16.0.0", + "marked": "^17.0.0", "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0 || ~0.16.0" } }, "node_modules/ngx-skeleton-loader": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/ngx-skeleton-loader/-/ngx-skeleton-loader-11.3.0.tgz", - "integrity": "sha512-MLm5shgXGiCA1W5NEqct6glBFx2AEgEKbk8pDyY15BsZ2zTGUwa5jw4pe6nJdrCj6xcl/d9oFTinQHrO0q+3RA==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ngx-skeleton-loader/-/ngx-skeleton-loader-12.0.0.tgz", + "integrity": "sha512-vGEytpLElYKSLovFHCJkwgPZOdy0lPqyejxuhVFcZJg9dsp07o0/NeM4/Nnc2oCDE8T/wkXSPIbrpKzfTDbMCQ==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -26622,9 +26845,9 @@ "license": "MIT" }, "node_modules/nx": { - "version": "22.4.5", - "resolved": "https://registry.npmjs.org/nx/-/nx-22.4.5.tgz", - "integrity": "sha512-l68kzhnemXXGCDS9/W8eccZ7Bzse9pw1oJ466pzDM89MbA6hEaOQ0p+eDXZI++iWl0T+lYJ56EDhO23syKzt9g==", + "version": "22.5.3", + "resolved": "https://registry.npmjs.org/nx/-/nx-22.5.3.tgz", + "integrity": "sha512-IaEPqdgaFBIr0Bfmnt6WAcX3t660sOuDXQ71lpoS8GgpD8cqX1LIW2ZyzEAdOvCP1iD6HCZehpofcVvaaL1GNQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -26634,12 +26857,12 @@ "@yarnpkg/parsers": "3.0.2", "@zkochan/js-yaml": "0.0.7", "axios": "^1.12.0", - "chalk": "^4.1.0", "cli-cursor": "3.1.0", "cli-spinners": "2.6.1", "cliui": "^8.0.1", "dotenv": "~16.4.5", "dotenv-expand": "~11.0.6", + "ejs": "^3.1.7", "enquirer": "~2.3.6", "figures": "3.2.0", "flat": "^5.0.2", @@ -26648,11 +26871,12 @@ "jest-diff": "^30.0.2", "jsonc-parser": "3.2.0", "lines-and-columns": "2.0.3", - "minimatch": "10.1.1", + "minimatch": "10.2.1", "node-machine-id": "1.1.12", "npm-run-path": "^4.0.1", "open": "^8.4.0", "ora": "5.3.0", + "picocolors": "^1.1.0", "resolve.exports": "2.0.3", "semver": "^7.6.3", "string-width": "^4.2.3", @@ -26670,20 +26894,20 @@ "nx-cloud": "bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "22.4.5", - "@nx/nx-darwin-x64": "22.4.5", - "@nx/nx-freebsd-x64": "22.4.5", - "@nx/nx-linux-arm-gnueabihf": "22.4.5", - "@nx/nx-linux-arm64-gnu": "22.4.5", - "@nx/nx-linux-arm64-musl": "22.4.5", - "@nx/nx-linux-x64-gnu": "22.4.5", - "@nx/nx-linux-x64-musl": "22.4.5", - "@nx/nx-win32-arm64-msvc": "22.4.5", - "@nx/nx-win32-x64-msvc": "22.4.5" + "@nx/nx-darwin-arm64": "22.5.3", + "@nx/nx-darwin-x64": "22.5.3", + "@nx/nx-freebsd-x64": "22.5.3", + "@nx/nx-linux-arm-gnueabihf": "22.5.3", + "@nx/nx-linux-arm64-gnu": "22.5.3", + "@nx/nx-linux-arm64-musl": "22.5.3", + "@nx/nx-linux-x64-gnu": "22.5.3", + "@nx/nx-linux-x64-musl": "22.5.3", + "@nx/nx-win32-arm64-msvc": "22.5.3", + "@nx/nx-win32-x64-msvc": "22.5.3" }, "peerDependencies": { - "@swc-node/register": "^1.8.0", - "@swc/core": "^1.3.85" + "@swc-node/register": "^1.11.1", + "@swc/core": "^1.15.8" }, "peerDependenciesMeta": { "@swc-node/register": { @@ -26716,6 +26940,29 @@ "tslib": "^2.4.0" } }, + "node_modules/nx/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/nx/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/nx/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -26829,13 +27076,13 @@ "license": "MIT" }, "node_modules/nx/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz", + "integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { "node": "20 || >=22" @@ -26930,25 +27177,30 @@ } }, "node_modules/nypm": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz", - "integrity": "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz", + "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", "devOptional": true, "license": "MIT", "dependencies": { - "citty": "^0.1.6", - "consola": "^3.4.2", + "citty": "^0.2.0", "pathe": "^2.0.3", - "pkg-types": "^2.3.0", - "tinyexec": "^1.0.1" + "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" }, "engines": { - "node": "^14.16.0 || >=16.10.0" + "node": ">=18" } }, + "node_modules/nypm/node_modules/citty": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz", + "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==", + "devOptional": true, + "license": "MIT" + }, "node_modules/oauth": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz", @@ -27551,6 +27803,16 @@ "node": ">=6" } }, + "node_modules/parse-imports-exports": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", + "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-statements": "1.0.11" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -27604,6 +27866,13 @@ "node": ">=0.10.0" } }, + "node_modules/parse-statements": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", + "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", + "dev": true, + "license": "MIT" + }, "node_modules/parse5": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", @@ -28936,15 +29205,15 @@ } }, "node_modules/prisma": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.0.tgz", - "integrity": "sha512-F3eX7K+tWpkbhl3l4+VkFtrwJlLXbAM+f9jolgoUZbFcm1DgHZ4cq9AgVEgUym2au5Ad/TDLN8lg83D+M10ycw==", + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.3.tgz", + "integrity": "sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "6.19.0", - "@prisma/engines": "6.19.0" + "@prisma/config": "6.19.3", + "@prisma/engines": "6.19.3" }, "bin": { "prisma": "build/index.js" @@ -29389,6 +29658,15 @@ "node": ">=4" } }, + "node_modules/redis-info": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redis-info/-/redis-info-3.1.0.tgz", + "integrity": "sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.11" + } + }, "node_modules/redis-parser": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", @@ -29647,15 +29925,15 @@ } }, "node_modules/replace-in-file": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-8.3.0.tgz", - "integrity": "sha512-4VhddQiMCPIuypiwHDTM+XHjZoVu9h7ngBbSCnwGRcwdHwxltjt/m//Ep3GDwqaOx1fDSrKFQ+n7uo4uVcEz9Q==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-8.4.0.tgz", + "integrity": "sha512-D28k8jy2LtUGbCzCnR3znajaTWIjJ/Uee3UdodzcHRxE7zn6NmYW/dcSqyivnsYU3W+MxdX6SbF28NvJ0GRoLA==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.3.0", - "glob": "^10.4.2", - "yargs": "^17.7.2" + "chalk": "^5.6.2", + "glob": "^13.0.0", + "yargs": "^18.0.0" }, "bin": { "replace-in-file": "bin/cli.js" @@ -29664,10 +29942,33 @@ "node": ">=18" } }, + "node_modules/replace-in-file/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/replace-in-file/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/replace-in-file/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", "engines": { @@ -29677,23 +29978,65 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/replace-in-file/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "node_modules/replace-in-file/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": ">=12" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/replace-in-file/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/replace-in-file/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/replace-in-file/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/require-directory": { @@ -31660,103 +32003,12 @@ "license": "MIT" }, "node_modules/static-eval": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", - "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", - "license": "MIT", - "dependencies": { - "escodegen": "^1.8.1" - } - }, - "node_modules/static-eval/node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/static-eval/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/static-eval/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "license": "MIT", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "license": "MIT", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/static-eval/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-eval/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.1.tgz", + "integrity": "sha512-MgWpQ/ZjGieSVB3eOJVs4OA2LT/q1vx98KPCTTQPzq/aLr0YUXTsgryTXr4SLfR0ZfUUCiedM9n/ABeDIyy4mA==", "license": "MIT", "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" + "escodegen": "^2.1.0" } }, "node_modules/statuses": { @@ -32104,9 +32356,9 @@ } }, "node_modules/stripe": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-20.3.0.tgz", - "integrity": "sha512-DYzcmV1MfYhycr1GwjCjeQVYk9Gu8dpxyTlu7qeDCsuguug7oUTxPsUQuZeSf/OPzK7pofqobvOKVqAwlpgf/Q==", + "version": "20.4.1", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-20.4.1.tgz", + "integrity": "sha512-axCguHItc8Sxt0HC6aSkdVRPffjYPV7EQqZRb2GkIa8FzWDycE7nHJM19C6xAIynH1Qp1/BHiopSi96jGBxT0w==", "license": "MIT", "engines": { "node": ">=16" @@ -32181,7 +32433,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -32210,9 +32461,9 @@ "license": "BSD-2-Clause" }, "node_modules/svgmap": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/svgmap/-/svgmap-2.14.0.tgz", - "integrity": "sha512-+Vklx4DO1uv1SFq6wnJWl/dRjX4uRT9CcsIHuADxAcZ+h5X1OSyDVbNdIu837fx5TtYYuaGRhWuFCXIioN/1ww==", + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/svgmap/-/svgmap-2.19.2.tgz", + "integrity": "sha512-mRqRcQiwwSTh9kTOPhjTmd3ywxA9aTfybBHGAoyuGn9CI9PnAQsuZ7H/2/VEIvgJhi1xM5IGBfk8i4/Ke4iTCQ==", "license": "MIT", "dependencies": { "svg-pan-zoom": "^3.6.2" @@ -32705,11 +32956,14 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", - "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", "devOptional": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { "version": "0.2.15", @@ -32817,11 +33071,12 @@ } }, "node_modules/token-types": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.4.tgz", - "integrity": "sha512-MD9MjpVNhVyH4fyd5rKphjvt/1qj+PtQUz65aFqAZA6XniWAuSFRjLk3e2VALEFlh9OwBpXUN7rfeqSnT/Fmkw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", "license": "MIT", "dependencies": { + "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" }, @@ -32955,18 +33210,15 @@ } }, "node_modules/ts-checker-rspack-plugin": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/ts-checker-rspack-plugin/-/ts-checker-rspack-plugin-1.2.6.tgz", - "integrity": "sha512-aAJIfoNr2cPu8G6mqp/oPoNlUT/LgNoqt2n3SsbxWG0TwQogbjsYsr2f/fdsufUDoGDu8Jolmpf3L4PmIH/cEg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-checker-rspack-plugin/-/ts-checker-rspack-plugin-1.3.0.tgz", + "integrity": "sha512-89oK/BtApjdid1j9CGjPGiYry+EZBhsnTAM481/8ipgr/y2IOgCbW1HPnan+fs5FnzlpUgf9dWGNZ4Ayw3Bd8A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", "@rspack/lite-tapable": "^1.1.0", "chokidar": "^3.6.0", - "is-glob": "^4.0.3", - "memfs": "^4.51.1", - "minimatch": "^9.0.5", + "memfs": "^4.56.10", "picocolors": "^1.1.1" }, "peerDependencies": { @@ -32979,16 +33231,6 @@ } } }, - "node_modules/ts-checker-rspack-plugin/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/ts-checker-rspack-plugin/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -33057,22 +33299,6 @@ "tslib": "2" } }, - "node_modules/ts-checker-rspack-plugin/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/ts-checker-rspack-plugin/node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -33367,6 +33593,24 @@ "node": ">=0.6.x" } }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, "node_modules/tuf-js": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", @@ -33589,9 +33833,9 @@ "license": "MIT" }, "node_modules/uint8array-extras": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz", - "integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", "license": "MIT", "engines": { "node": ">=18" @@ -33620,18 +33864,18 @@ } }, "node_modules/underscore": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", - "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "license": "MIT" }, "node_modules/undici": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", - "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", "license": "MIT", "engines": { - "node": ">=18.17" + "node": ">=20.18.1" } }, "node_modules/undici-types": { @@ -35225,6 +35469,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -35375,9 +35620,9 @@ } }, "node_modules/yahoo-finance2": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.13.0.tgz", - "integrity": "sha512-czBj2q/MD68YEsB7aXNnGhJvWxYZn01O5r/i7VYiQV2m2sWwhca6tKgjwf/LT7zHHEVxhKNiGLB46glLnmq9Ag==", + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.13.2.tgz", + "integrity": "sha512-aAOJEjuLClfDxVPRKxjcwFoyzMr8BE/svgUqr5IjnQR+kppYbKO92Wl3SbAGz5DRghy6FiUfqi5FBDSBA/e2jg==", "license": "MIT", "dependencies": { "@deno/shim-deno": "~0.18.0", diff --git a/package.json b/package.json index 0f43fff08..686065630 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.237.0", + "version": "2.253.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", @@ -43,10 +43,11 @@ "start:production": "npm run database:migrate && npm run database:seed && node main", "start:server": "nx run api:copy-assets && nx run api:serve --watch", "start:storybook": "nx run ui:storybook", - "test": "npm run test:api && npm run test:common", + "test": "npx dotenv-cli -e .env.example -- npx nx run-many --target=test --all --parallel=4", "test:api": "npx dotenv-cli -e .env.example -- nx test api", "test:common": "npx dotenv-cli -e .env.example -- nx test common", "test:single": "nx run api:test --test-file object.helper.spec.ts", + "test:ui": "npx dotenv-cli -e .env.example -- nx test ui", "ts-node": "ts-node", "update": "nx migrate latest", "watch:server": "nx run api:copy-assets && nx run api:build --watch", @@ -65,26 +66,29 @@ "@angular/platform-browser-dynamic": "21.1.1", "@angular/router": "21.1.1", "@angular/service-worker": "21.1.1", + "@bull-board/api": "6.20.3", + "@bull-board/express": "6.20.3", + "@bull-board/nestjs": "6.20.3", "@codewithdan/observable-store": "2.2.15", "@date-fns/utc": "2.1.1", "@internationalized/number": "3.6.5", - "@ionic/angular": "8.7.8", + "@ionic/angular": "8.8.1", "@keyv/redis": "4.4.0", "@nestjs/bull": "11.0.4", - "@nestjs/cache-manager": "3.0.1", - "@nestjs/common": "11.1.8", - "@nestjs/config": "4.0.2", - "@nestjs/core": "11.1.8", + "@nestjs/cache-manager": "3.1.0", + "@nestjs/common": "11.1.14", + "@nestjs/config": "4.0.3", + "@nestjs/core": "11.1.14", "@nestjs/event-emitter": "3.0.1", - "@nestjs/jwt": "11.0.1", + "@nestjs/jwt": "11.0.2", "@nestjs/passport": "11.0.5", - "@nestjs/platform-express": "11.1.8", - "@nestjs/schedule": "6.0.1", + "@nestjs/platform-express": "11.1.14", + "@nestjs/schedule": "6.1.1", "@nestjs/serve-static": "5.0.4", "@openrouter/ai-sdk-provider": "0.7.2", - "@prisma/client": "6.19.0", - "@simplewebauthn/browser": "13.1.0", - "@simplewebauthn/server": "13.1.1", + "@prisma/client": "6.19.3", + "@simplewebauthn/browser": "13.2.2", + "@simplewebauthn/server": "13.2.2", "ai": "4.3.16", "alphavantage": "2.2.0", "big.js": "7.0.1", @@ -95,12 +99,13 @@ "chartjs-chart-treemap": "3.1.0", "chartjs-plugin-annotation": "3.1.0", "chartjs-plugin-datalabels": "2.2.0", - "cheerio": "1.0.0", + "cheerio": "1.2.0", "class-transformer": "0.5.1", - "class-validator": "0.14.3", + "class-validator": "0.15.1", "color": "5.0.3", + "cookie-parser": "1.4.7", "countries-and-timezones": "3.8.0", - "countries-list": "3.2.2", + "countries-list": "3.3.0", "countup.js": "2.9.0", "date-fns": "4.1.0", "dotenv": "17.2.3", @@ -112,14 +117,14 @@ "helmet": "7.0.0", "http-status-codes": "2.3.0", "ionicons": "8.0.13", - "jsonpath": "1.1.1", + "jsonpath": "1.2.1", "lodash": "4.17.23", - "marked": "17.0.1", + "marked": "17.0.2", "ms": "3.0.0-canary.1", - "ng-extract-i18n-merge": "3.2.1", + "ng-extract-i18n-merge": "3.3.0", "ngx-device-detector": "11.0.0", - "ngx-markdown": "21.0.1", - "ngx-skeleton-loader": "11.3.0", + "ngx-markdown": "21.1.0", + "ngx-skeleton-loader": "12.0.0", "open-color": "1.9.1", "papaparse": "5.3.1", "passport": "0.7.0", @@ -129,11 +134,11 @@ "passport-openidconnect": "0.1.2", "reflect-metadata": "0.2.2", "rxjs": "7.8.1", - "stripe": "20.3.0", - "svgmap": "2.14.0", + "stripe": "20.4.1", + "svgmap": "2.19.2", "tablemark": "4.1.0", "twitter-api-v2": "1.29.0", - "yahoo-finance2": "3.13.0", + "yahoo-finance2": "3.13.2", "zone.js": "0.16.0" }, "devDependencies": { @@ -151,22 +156,23 @@ "@eslint/eslintrc": "3.3.1", "@eslint/js": "9.35.0", "@nestjs/schematics": "11.0.9", - "@nestjs/testing": "11.1.8", - "@nx/angular": "22.4.5", - "@nx/eslint-plugin": "22.4.5", - "@nx/jest": "22.4.5", - "@nx/js": "22.4.5", - "@nx/module-federation": "22.4.5", - "@nx/nest": "22.4.5", - "@nx/node": "22.4.5", - "@nx/storybook": "22.4.5", - "@nx/web": "22.4.5", - "@nx/workspace": "22.4.5", + "@nestjs/testing": "11.1.14", + "@nx/angular": "22.5.3", + "@nx/eslint-plugin": "22.5.3", + "@nx/jest": "22.5.3", + "@nx/js": "22.5.3", + "@nx/module-federation": "22.5.3", + "@nx/nest": "22.5.3", + "@nx/node": "22.5.3", + "@nx/storybook": "22.5.3", + "@nx/web": "22.5.3", + "@nx/workspace": "22.5.3", "@schematics/angular": "21.1.1", "@storybook/addon-docs": "10.1.10", "@storybook/angular": "10.1.10", - "@trivago/prettier-plugin-sort-imports": "5.2.2", + "@trivago/prettier-plugin-sort-imports": "6.0.2", "@types/big.js": "6.2.2", + "@types/cookie-parser": "1.4.10", "@types/fast-redact": "3.0.4", "@types/google-spreadsheet": "3.1.5", "@types/jest": "30.0.0", @@ -174,7 +180,7 @@ "@types/lodash": "4.17.23", "@types/node": "22.15.17", "@types/papaparse": "5.3.7", - "@types/passport-google-oauth20": "2.0.16", + "@types/passport-google-oauth20": "2.0.17", "@types/passport-openidconnect": "0.1.3", "@typescript-eslint/eslint-plugin": "8.43.0", "@typescript-eslint/parser": "8.43.0", @@ -186,13 +192,13 @@ "jest": "30.2.0", "jest-environment-jsdom": "30.2.0", "jest-preset-angular": "16.0.0", - "nx": "22.4.5", + "nx": "22.5.3", "prettier": "3.8.1", "prettier-plugin-organize-attributes": "1.0.0", - "prisma": "6.19.0", + "prisma": "6.19.3", "react": "18.2.0", "react-dom": "18.2.0", - "replace-in-file": "8.3.0", + "replace-in-file": "8.4.0", "shx": "0.4.0", "storybook": "10.1.10", "ts-jest": "29.4.0", diff --git a/prisma/migrations/20260321200654_added_index_for_type_to_order/migration.sql b/prisma/migrations/20260321200654_added_index_for_type_to_order/migration.sql new file mode 100644 index 000000000..ba4d1b1ff --- /dev/null +++ b/prisma/migrations/20260321200654_added_index_for_type_to_order/migration.sql @@ -0,0 +1,2 @@ +-- CreateIndex +CREATE INDEX "Order_type_idx" ON "Order"("type"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 232dde9ca..50aac91fb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -158,6 +158,7 @@ model Order { @@index([accountId]) @@index([date]) @@index([isDraft]) + @@index([type]) @@index([userId]) } diff --git a/test/import/ok/jnug-buy-and-sell-and-buy-and-sell.json b/test/import/ok/jnug-buy-and-sell-and-buy-and-sell.json new file mode 100644 index 000000000..2a14d8afe --- /dev/null +++ b/test/import/ok/jnug-buy-and-sell-and-buy-and-sell.json @@ -0,0 +1,58 @@ +{ + "meta": { + "date": "2026-02-07T02:09:15.272Z", + "version": "dev" + }, + "activities": [ + { + "currency": "USD", + "dataSource": "YAHOO", + "date": "2025-12-11T05:00:00.000Z", + "fee": 1, + "id": "cea33621-9f4b-4cea-9eb7-be38264888aa", + "quantity": 9, + "symbol": "JNUG", + "type": "BUY", + "unitPrice": 209.45 + }, + { + "currency": "USD", + "dataSource": "YAHOO", + "date": "2025-12-18T05:00:00.000Z", + "fee": 1, + "id": "53be2e35-a0af-476c-9e63-9b1a437114a4", + "quantity": 9, + "symbol": "JNUG", + "type": "SELL", + "unitPrice": 210 + }, + { + "currency": "USD", + "dataSource": "YAHOO", + "date": "2025-12-18T05:00:00.000Z", + "fee": 1, + "id": "6648eeeb-8ea5-46b6-9a30-f278a9ed477b", + "quantity": 10, + "symbol": "JNUG", + "type": "BUY", + "unitPrice": 204.11 + }, + { + "currency": "USD", + "dataSource": "YAHOO", + "date": "2025-12-28T05:00:00.000Z", + "fee": 1, + "id": "861e736d-0086-496c-8f85-31328479cf63", + "quantity": 10, + "symbol": "JNUG", + "type": "SELL", + "unitPrice": 208.01 + } + ], + "user": { + "settings": { + "currency": "USD", + "performanceCalculationType": "ROAI" + } + } +} diff --git a/test/import/ok/penthouse-apartment.json b/test/import/ok/penthouse-apartment.json index 0c35521e6..3b5e5420b 100644 --- a/test/import/ok/penthouse-apartment.json +++ b/test/import/ok/penthouse-apartment.json @@ -21,10 +21,8 @@ "isin": null, "marketData": [], "name": "Penthouse Apartment", - "scraperConfiguration": null, "sectors": [], "symbol": "7e91b7d4-1430-4212-8380-289a06c9bbc1", - "symbolMapping": {}, "url": null } ], diff --git a/test/import/ok/sample.json b/test/import/ok/sample.json index bc2798718..be385812a 100644 --- a/test/import/ok/sample.json +++ b/test/import/ok/sample.json @@ -41,10 +41,8 @@ "isin": null, "marketData": [], "name": "Account Opening Fee", - "scraperConfiguration": null, "sectors": [], "symbol": "14a69cb9-1e31-43fa-b320-83703d8ed74b", - "symbolMapping": {}, "url": null }, { @@ -63,10 +61,8 @@ "isin": null, "marketData": [], "name": "Penthouse Apartment", - "scraperConfiguration": null, "sectors": [], "symbol": "7e91b7d4-1430-4212-8380-289a06c9bbc1", - "symbolMapping": {}, "url": null } ],
    - @if (hasPermissionToDeleteItem) { + @if (hasPermissionToDeleteItem()) {
    -
    {{ element?.name | titlecase }}
    +
    {{ prettifyAssetName(element?.name) }}