Browse Source

Merge branch 'main' into translate-zh-fee-ratio

pull/6348/head
Thomas Kaul 1 month ago
committed by GitHub
parent
commit
fc2d1b6f00
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 38
      CHANGELOG.md
  2. 4
      apps/api/src/app/admin/admin.service.ts
  3. 134
      apps/api/src/app/import/import.service.ts
  4. 25
      apps/api/src/app/order/order.controller.ts
  5. 43
      apps/api/src/assets/cryptocurrencies/cryptocurrencies.json
  6. 122
      apps/api/src/services/data-provider/data-provider.service.ts
  7. 7
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  8. 7
      apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html
  9. 12
      apps/client/src/app/pages/about/changelog/changelog-page.component.ts
  10. 12
      apps/client/src/app/pages/about/oss-friends/oss-friends-page.component.ts
  11. 12
      apps/client/src/app/pages/admin/admin-page.component.ts
  12. 15
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  13. 8
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html
  14. 8
      apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts
  15. 20
      apps/client/src/locales/messages.ca.xlf
  16. 20
      apps/client/src/locales/messages.de.xlf
  17. 28
      apps/client/src/locales/messages.es.xlf
  18. 20
      apps/client/src/locales/messages.fr.xlf
  19. 20
      apps/client/src/locales/messages.it.xlf
  20. 20
      apps/client/src/locales/messages.ko.xlf
  21. 20
      apps/client/src/locales/messages.nl.xlf
  22. 20
      apps/client/src/locales/messages.pl.xlf
  23. 20
      apps/client/src/locales/messages.pt.xlf
  24. 20
      apps/client/src/locales/messages.tr.xlf
  25. 20
      apps/client/src/locales/messages.uk.xlf
  26. 19
      apps/client/src/locales/messages.xlf
  27. 20
      apps/client/src/locales/messages.zh.xlf
  28. 5
      libs/common/src/lib/config.ts
  29. 3
      libs/common/src/lib/types/market-data-preset.type.ts
  30. 10
      libs/ui/src/lib/account-balances/account-balances.component.html
  31. 68
      libs/ui/src/lib/account-balances/account-balances.component.ts
  32. 2
      libs/ui/src/lib/accounts-table/accounts-table.component.ts
  33. 8
      libs/ui/src/lib/activities-filter/activities-filter.component.html
  34. 83
      libs/ui/src/lib/activities-filter/activities-filter.component.ts
  35. 47
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts
  36. 3
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html
  37. 2
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts
  38. 24
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html
  39. 60
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.spec.ts
  40. 133
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts
  41. 12
      package-lock.json
  42. 7
      package.json

38
CHANGELOG.md

@ -7,14 +7,50 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Changed
- Improved the language localization for Chinese (`zh`)
## 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
- 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 custom cryptocurrencies defined in the database
- Added support for the cryptocurrency _Sky_ - Added support for the cryptocurrency _Sky_
### Changed ### Changed
- Improved the language localization for Chinese (`zh`) - 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` - Upgraded `ngx-markdown` from version `21.0.1` to `21.1.0`
## 2.239.0 - 2026-02-15 ## 2.239.0 - 2026-02-15

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

@ -225,6 +225,10 @@ export class AdminService {
presetId === 'ETF_WITHOUT_SECTORS' presetId === 'ETF_WITHOUT_SECTORS'
) { ) {
filters = [{ id: 'ETF', type: 'ASSET_SUB_CLASS' }]; filters = [{ id: 'ETF', type: 'ASSET_SUB_CLASS' }];
} else if (presetId === 'NO_ACTIVITIES') {
where.activities = {
none: {}
};
} }
const searchQuery = filters.find(({ type }) => { const searchQuery = filters.find(({ type }) => {

134
apps/api/src/app/import/import.service.ts

@ -3,7 +3,6 @@ import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { PlatformService } from '@ghostfolio/api/app/platform/platform.service'; import { PlatformService } from '@ghostfolio/api/app/platform/platform.service';
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { ApiService } from '@ghostfolio/api/services/api/api.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 { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.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 { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
@ -33,7 +32,7 @@ import {
} from '@ghostfolio/common/types'; } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { DataSource, Prisma, SymbolProfile } from '@prisma/client'; import { DataSource, Prisma } from '@prisma/client';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { endOfToday, isAfter, isSameSecond, parseISO } from 'date-fns'; import { endOfToday, isAfter, isSameSecond, parseISO } from 'date-fns';
import { omit, uniqBy } from 'lodash'; import { omit, uniqBy } from 'lodash';
@ -46,7 +45,6 @@ export class ImportService {
public constructor( public constructor(
private readonly accountService: AccountService, private readonly accountService: AccountService,
private readonly apiService: ApiService, private readonly apiService: ApiService,
private readonly configurationService: ConfigurationService,
private readonly dataGatheringService: DataGatheringService, private readonly dataGatheringService: DataGatheringService,
private readonly dataProviderService: DataProviderService, private readonly dataProviderService: DataProviderService,
private readonly exchangeRateDataService: ExchangeRateDataService, private readonly exchangeRateDataService: ExchangeRateDataService,
@ -395,7 +393,7 @@ export class ImportService {
} }
} }
const assetProfiles = await this.validateActivities({ const assetProfiles = await this.dataProviderService.validateActivities({
activitiesDto, activitiesDto,
assetProfilesWithMarketDataDto, assetProfilesWithMarketDataDto,
maxActivitiesToImport, maxActivitiesToImport,
@ -729,132 +727,4 @@ export class ImportService {
return uniqueAccountIds.size === 1; return uniqueAccountIds.size === 1;
} }
private async validateActivities({
activitiesDto,
assetProfilesWithMarketDataDto,
maxActivitiesToImport,
user
}: {
activitiesDto: Partial<CreateOrderDto>[];
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<SymbolProfile>;
} = {};
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<SymbolProfile> = { 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;
}
} }

25
apps/api/src/app/order/order.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 { 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 { 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 { 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 { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service'; import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
@ -46,6 +47,7 @@ import { OrderService } from './order.service';
export class OrderController { export class OrderController {
public constructor( public constructor(
private readonly apiService: ApiService, private readonly apiService: ApiService,
private readonly dataProviderService: DataProviderService,
private readonly dataGatheringService: DataGatheringService, private readonly dataGatheringService: DataGatheringService,
private readonly impersonationService: ImpersonationService, private readonly impersonationService: ImpersonationService,
private readonly orderService: OrderService, private readonly orderService: OrderService,
@ -190,6 +192,29 @@ export class OrderController {
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInRequestInterceptor)
public async createOrder(@Body() data: CreateOrderDto): Promise<OrderModel> { public async createOrder(@Body() data: CreateOrderDto): Promise<OrderModel> {
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 currency = data.currency;
const customCurrency = data.customCurrency; const customCurrency = data.customCurrency;
const dataSource = data.dataSource; const dataSource = data.dataSource;

43
apps/api/src/assets/cryptocurrencies/cryptocurrencies.json

@ -421,6 +421,7 @@
"AGS": "Aegis", "AGS": "Aegis",
"AGT": "Alaya Governance Token", "AGT": "Alaya Governance Token",
"AGURI": "Aguri-Chan", "AGURI": "Aguri-Chan",
"AGUSTO": "Agusto",
"AGV": "Astra Guild Ventures", "AGV": "Astra Guild Ventures",
"AGVC": "AgaveCoin", "AGVC": "AgaveCoin",
"AGVE": "Agave", "AGVE": "Agave",
@ -662,6 +663,7 @@
"ALN": "Aluna", "ALN": "Aluna",
"ALNV1": "Aluna v1", "ALNV1": "Aluna v1",
"ALOHA": "Aloha", "ALOHA": "Aloha",
"ALOKA": "ALOKA",
"ALON": "Alon", "ALON": "Alon",
"ALOR": "The Algorix", "ALOR": "The Algorix",
"ALOT": "Dexalot", "ALOT": "Dexalot",
@ -708,6 +710,7 @@
"AMADEUS": "AMADEUS", "AMADEUS": "AMADEUS",
"AMAL": "AMAL", "AMAL": "AMAL",
"AMAPT": "Amnis Finance", "AMAPT": "Amnis Finance",
"AMARA": "AMARA",
"AMATEN": "Amaten", "AMATEN": "Amaten",
"AMATO": "AMATO", "AMATO": "AMATO",
"AMAZINGTEAM": "AmazingTeamDAO", "AMAZINGTEAM": "AmazingTeamDAO",
@ -1344,6 +1347,7 @@
"AZIT": "Azit", "AZIT": "Azit",
"AZNX": "AstraZeneca xStock", "AZNX": "AstraZeneca xStock",
"AZR": "Azure", "AZR": "Azure",
"AZTEC": "AZTEC",
"AZU": "Azultec", "AZU": "Azultec",
"AZUKI": "Azuki", "AZUKI": "Azuki",
"AZUKI2": "AZUKI 2.0", "AZUKI2": "AZUKI 2.0",
@ -1373,6 +1377,7 @@
"BABI": "Babylons", "BABI": "Babylons",
"BABL": "Babylon Finance", "BABL": "Babylon Finance",
"BABY": "Babylon", "BABY": "Babylon",
"BABY4": "Baby 4",
"BABYANDY": "Baby Andy", "BABYANDY": "Baby Andy",
"BABYASTER": "Baby Aster", "BABYASTER": "Baby Aster",
"BABYB": "Baby Bali", "BABYB": "Baby Bali",
@ -2342,6 +2347,7 @@
"BNPL": "BNPL Pay", "BNPL": "BNPL Pay",
"BNR": "BiNeuro", "BNR": "BiNeuro",
"BNRTX": "BnrtxCoin", "BNRTX": "BnrtxCoin",
"BNRY": "Binary Coin",
"BNS": "BNS token", "BNS": "BNS token",
"BNSAI": "bonsAI Network", "BNSAI": "bonsAI Network",
"BNSD": "BNSD Finance", "BNSD": "BNSD Finance",
@ -2526,9 +2532,10 @@
"BOSSCOQ": "THE COQFATHER", "BOSSCOQ": "THE COQFATHER",
"BOST": "BoostCoin", "BOST": "BoostCoin",
"BOSU": "Bosu Inu", "BOSU": "Bosu Inu",
"BOT": "Bot Planet", "BOT": "HyperBot",
"BOTC": "BotChain", "BOTC": "BotChain",
"BOTIFY": "BOTIFY", "BOTIFY": "BOTIFY",
"BOTPLANET": "Bot Planet",
"BOTS": "ArkDAO", "BOTS": "ArkDAO",
"BOTTO": "Botto", "BOTTO": "Botto",
"BOTX": "BOTXCOIN", "BOTX": "BOTXCOIN",
@ -3201,6 +3208,7 @@
"CATCO": "CatCoin", "CATCO": "CatCoin",
"CATCOIN": "CatCoin", "CATCOIN": "CatCoin",
"CATCOINETH": "Catcoin", "CATCOINETH": "Catcoin",
"CATCOINIO": "Catcoin",
"CATCOINOFSOL": "Cat Coin", "CATCOINOFSOL": "Cat Coin",
"CATCOINV2": "CatCoin Cash", "CATCOINV2": "CatCoin Cash",
"CATDOG": "Cat-Dog", "CATDOG": "Cat-Dog",
@ -3583,6 +3591,7 @@
"CIC": "Crazy Internet Coin", "CIC": "Crazy Internet Coin",
"CICHAIN": "CIChain", "CICHAIN": "CIChain",
"CIF": "Crypto Improvement Fund", "CIF": "Crypto Improvement Fund",
"CIFRON": "Cipher Mining (Ondo Tokenized)",
"CIG": "cig", "CIG": "cig",
"CIM": "COINCOME", "CIM": "COINCOME",
"CIN": "CinderCoin", "CIN": "CinderCoin",
@ -3718,6 +3727,7 @@
"CMPT": "Spatial Computing", "CMPT": "Spatial Computing",
"CMPV2": "Caduceus Protocol", "CMPV2": "Caduceus Protocol",
"CMQ": "Communique", "CMQ": "Communique",
"CMR": "U.S Critical Mineral Reserve",
"CMS": "COMSA", "CMS": "COMSA",
"CMSN": "The Commission", "CMSN": "The Commission",
"CMT": "CyberMiles", "CMT": "CyberMiles",
@ -4630,6 +4640,7 @@
"DEFIL": "DeFIL", "DEFIL": "DeFIL",
"DEFILAB": "Defi", "DEFILAB": "Defi",
"DEFISCALE": "DeFiScale", "DEFISCALE": "DeFiScale",
"DEFISSI": "DEFI.ssi",
"DEFIT": "Digital Fitness", "DEFIT": "Digital Fitness",
"DEFLA": "Defla", "DEFLA": "Defla",
"DEFLCT": "Deflect", "DEFLCT": "Deflect",
@ -6323,7 +6334,7 @@
"FIFTY": "FIFTYONEFIFTY", "FIFTY": "FIFTYONEFIFTY",
"FIG": "FlowCom", "FIG": "FlowCom",
"FIGH": "FIGHT FIGHT FIGHT", "FIGH": "FIGHT FIGHT FIGHT",
"FIGHT": "Fight to MAGA", "FIGHT2MAGA": "Fight to MAGA",
"FIGHTMAGA": "FIGHT MAGA", "FIGHTMAGA": "FIGHT MAGA",
"FIGHTPEPE": "FIGHT PEPE", "FIGHTPEPE": "FIGHT PEPE",
"FIGHTRUMP": "FIGHT TRUMP", "FIGHTRUMP": "FIGHT TRUMP",
@ -8039,6 +8050,7 @@
"HONOR": "HonorLand", "HONOR": "HonorLand",
"HONX": "Honeywell xStock", "HONX": "Honeywell xStock",
"HOODOG": "Hoodog", "HOODOG": "Hoodog",
"HOODON": "Robinhood Markets (Ondo Tokenized)",
"HOODRAT": "Hoodrat Coin", "HOODRAT": "Hoodrat Coin",
"HOODX": "Robinhood xStock", "HOODX": "Robinhood xStock",
"HOOF": "Metaderby Hoof", "HOOF": "Metaderby Hoof",
@ -8395,6 +8407,7 @@
"IMS": "Independent Money System", "IMS": "Independent Money System",
"IMST": "Imsmart", "IMST": "Imsmart",
"IMT": "Immortal Token", "IMT": "Immortal Token",
"IMU": "Immunefi",
"IMUSIFY": "imusify", "IMUSIFY": "imusify",
"IMVR": "ImmVRse", "IMVR": "ImmVRse",
"IMX": "Immutable X", "IMX": "Immutable X",
@ -8750,6 +8763,7 @@
"JFIVE": "Jonny Five", "JFIVE": "Jonny Five",
"JFOX": "JuniperFox AI", "JFOX": "JuniperFox AI",
"JFP": "JUSTICE FOR PEANUT", "JFP": "JUSTICE FOR PEANUT",
"JGGL": "JGGL Token",
"JGLP": "Jones GLP", "JGLP": "Jones GLP",
"JGN": "Juggernaut", "JGN": "Juggernaut",
"JHH": "Jen-Hsun Huang", "JHH": "Jen-Hsun Huang",
@ -9891,7 +9905,7 @@
"LRN": "Loopring [NEO]", "LRN": "Loopring [NEO]",
"LRT": "LandRocker", "LRT": "LandRocker",
"LSC": "LS Coin", "LSC": "LS Coin",
"LSD": "Pontem Liquidswap", "LSD": "LSD",
"LSDOGE": "LSDoge", "LSDOGE": "LSDoge",
"LSETH": "Liquid Staked ETH", "LSETH": "Liquid Staked ETH",
"LSHARE": "LSHARE", "LSHARE": "LSHARE",
@ -10167,8 +10181,7 @@
"MANUSAI": "Manus AI Agent", "MANUSAI": "Manus AI Agent",
"MANYU": "Manyu", "MANYU": "Manyu",
"MANYUDOG": "MANYU", "MANYUDOG": "MANYU",
"MAO": "MAO", "MAO": "Mao",
"MAOMEME": "Mao",
"MAOW": "MAOW", "MAOW": "MAOW",
"MAP": "MAP Protocol", "MAP": "MAP Protocol",
"MAPC": "MapCoin", "MAPC": "MapCoin",
@ -10631,6 +10644,7 @@
"MICRO": "Micro GPT", "MICRO": "Micro GPT",
"MICRODOGE": "MicroDoge", "MICRODOGE": "MicroDoge",
"MICROMINES": "Micromines", "MICROMINES": "Micromines",
"MICROVISION": "MicroVisionChain",
"MIDAI": "Midway AI", "MIDAI": "Midway AI",
"MIDAS": "Midas", "MIDAS": "Midas",
"MIDASDOLLAR": "Midas Dollar Share", "MIDASDOLLAR": "Midas Dollar Share",
@ -13146,6 +13160,7 @@
"PONKE": "Ponke", "PONKE": "Ponke",
"PONKEBNB": "Ponke BNB", "PONKEBNB": "Ponke BNB",
"PONKEI": "Chinese Ponkei the Original", "PONKEI": "Chinese Ponkei the Original",
"PONTEM": "Pontem Liquidswap",
"PONYO": "Ponyo Impact", "PONYO": "Ponyo Impact",
"PONZI": "Ponzi", "PONZI": "Ponzi",
"PONZIO": "Ponzio The Cat", "PONZIO": "Ponzio The Cat",
@ -13573,6 +13588,7 @@
"QNX": "QueenDex Coin", "QNX": "QueenDex Coin",
"QOBI": "Qobit", "QOBI": "Qobit",
"QOM": "Shiba Predator", "QOM": "Shiba Predator",
"QONE": "QONE",
"QOOB": "QOOBER", "QOOB": "QOOBER",
"QORA": "QoraCoin", "QORA": "QoraCoin",
"QORPO": "QORPO WORLD", "QORPO": "QORPO WORLD",
@ -15153,6 +15169,7 @@
"SNAP": "SnapEx", "SNAP": "SnapEx",
"SNAPCAT": "Snapcat", "SNAPCAT": "Snapcat",
"SNAPKERO": "SNAP", "SNAPKERO": "SNAP",
"SNAPON": "Snap (Ondo Tokenized)",
"SNB": "SynchroBitcoin", "SNB": "SynchroBitcoin",
"SNC": "SunContract", "SNC": "SunContract",
"SNCT": "SnakeCity", "SNCT": "SnakeCity",
@ -15380,7 +15397,7 @@
"SP8DE": "Sp8de", "SP8DE": "Sp8de",
"SPA": "Sperax", "SPA": "Sperax",
"SPAC": "SPACE DOGE", "SPAC": "SPACE DOGE",
"SPACE": "MicroVisionChain", "SPACE": "Spacecoin",
"SPACECOIN": "SpaceCoin", "SPACECOIN": "SpaceCoin",
"SPACED": "SPACE DRAGON", "SPACED": "SPACE DRAGON",
"SPACEHAMSTER": "Space Hamster", "SPACEHAMSTER": "Space Hamster",
@ -15868,6 +15885,7 @@
"SUPERCYCLE": "Crypto SuperCycle", "SUPERCYCLE": "Crypto SuperCycle",
"SUPERDAPP": "SuperDapp", "SUPERDAPP": "SuperDapp",
"SUPERF": "SUPER FLOKI", "SUPERF": "SUPER FLOKI",
"SUPERFL": "Superfluid",
"SUPERGROK": "SuperGrok", "SUPERGROK": "SuperGrok",
"SUPEROETHB": "Super OETH", "SUPEROETHB": "Super OETH",
"SUPERT": "Super Trump", "SUPERT": "Super Trump",
@ -16790,6 +16808,7 @@
"TSLAON": "Tesla (Ondo Tokenized)", "TSLAON": "Tesla (Ondo Tokenized)",
"TSLAX": "Tesla xStock", "TSLAX": "Tesla xStock",
"TSLT": "Tamkin", "TSLT": "Tamkin",
"TSMON": "Taiwan Semiconductor Manufacturing (Ondo Tokenized)",
"TSN": "Tsunami Exchange Token", "TSN": "Tsunami Exchange Token",
"TSO": "Thesirion", "TSO": "Thesirion",
"TSOTCHKE": "tsotchke", "TSOTCHKE": "tsotchke",
@ -17181,8 +17200,10 @@
"USDL": "Lift Dollar", "USDL": "Lift Dollar",
"USDM": "USDM", "USDM": "USDM",
"USDMA": "USD mars", "USDMA": "USD mars",
"USDN": "Neutral AI", "USDN": "Ultimate Synthetic Delta Neutral",
"USDNEUTRAL": "Neutral AI",
"USDO": "USD Open Dollar", "USDO": "USD Open Dollar",
"USDON": "U.S. Dollar Tokenized Currency (Ondo)",
"USDP": "Pax Dollar", "USDP": "Pax Dollar",
"USDPLUS": "Overnight.fi USD+", "USDPLUS": "Overnight.fi USD+",
"USDQ": "Quantoz USDQ", "USDQ": "Quantoz USDQ",
@ -17456,6 +17477,7 @@
"VIDZ": "PureVidz", "VIDZ": "PureVidz",
"VIEW": "Viewly", "VIEW": "Viewly",
"VIG": "TheVig", "VIG": "TheVig",
"VIGI": "Vigi",
"VIK": "VIKTAMA", "VIK": "VIKTAMA",
"VIKITA": "VIKITA", "VIKITA": "VIKITA",
"VIKKY": "VikkyToken", "VIKKY": "VikkyToken",
@ -17513,6 +17535,7 @@
"VLC": "Volcano Uni", "VLC": "Volcano Uni",
"VLDY": "Validity", "VLDY": "Validity",
"VLK": "Vulkania", "VLK": "Vulkania",
"VLR": "Velora",
"VLS": "Veles", "VLS": "Veles",
"VLT": "Veltor", "VLT": "Veltor",
"VLTC": "Venus LTC", "VLTC": "Venus LTC",
@ -17733,6 +17756,7 @@
"WANUSDT": "wanUSDT", "WANUSDT": "wanUSDT",
"WAP": "Wet Ass Pussy", "WAP": "Wet Ass Pussy",
"WAR": "WAR", "WAR": "WAR",
"WARD": "Warden",
"WARP": "WarpCoin", "WARP": "WarpCoin",
"WARPED": "Warped Games", "WARPED": "Warped Games",
"WARPIE": "Warpie", "WARPIE": "Warpie",
@ -18494,6 +18518,7 @@
"XP": "Xphere", "XP": "Xphere",
"XPA": "XPA", "XPA": "XPA",
"XPARTY": "X Party", "XPARTY": "X Party",
"XPASS": "XPASS Token",
"XPAT": "Bitnation Pangea", "XPAT": "Bitnation Pangea",
"XPAY": "Wallet Pay", "XPAY": "Wallet Pay",
"XPB": "Pebble Coin", "XPB": "Pebble Coin",
@ -18869,8 +18894,7 @@
"ZEBU": "ZEBU", "ZEBU": "ZEBU",
"ZEC": "ZCash", "ZEC": "ZCash",
"ZECD": "ZCashDarkCoin", "ZECD": "ZCashDarkCoin",
"ZED": "ZED Token", "ZED": "ZedCoins",
"ZEDCOIN": "ZedCoin",
"ZEDD": "ZedDex", "ZEDD": "ZedDex",
"ZEDTOKEN": "Zed Token", "ZEDTOKEN": "Zed Token",
"ZEDX": "ZEDX Сoin", "ZEDX": "ZEDX Сoin",
@ -19108,6 +19132,7 @@
"币安人生": "币安人生", "币安人生": "币安人生",
"恶俗企鹅": "恶俗企鹅", "恶俗企鹅": "恶俗企鹅",
"我踏马来了": "我踏马来了", "我踏马来了": "我踏马来了",
"狗屎": "狗屎",
"老子": "老子", "老子": "老子",
"雪球": "雪球", "雪球": "雪球",
"黑马": "黑马" "黑马": "黑马"

122
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 { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface';
@ -10,8 +11,10 @@ import {
PROPERTY_API_KEY_GHOSTFOLIO, PROPERTY_API_KEY_GHOSTFOLIO,
PROPERTY_DATA_SOURCE_MAPPING PROPERTY_DATA_SOURCE_MAPPING
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { CreateOrderDto } from '@ghostfolio/common/dtos';
import { import {
DATE_FORMAT, DATE_FORMAT,
getAssetProfileIdentifier,
getCurrencyFromSymbol, getCurrencyFromSymbol,
getStartOfUtcDate, getStartOfUtcDate,
isCurrency, isCurrency,
@ -185,6 +188,125 @@ export class DataProviderService implements OnModuleInit {
return dataSources.sort(); return dataSources.sort();
} }
public async validateActivities({
activitiesDto,
assetProfilesWithMarketDataDto,
maxActivitiesToImport,
user
}: {
activitiesDto: Pick<
Partial<CreateOrderDto>,
'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<SymbolProfile>;
} = {};
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<SymbolProfile> = { 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({ public async getDividends({
dataSource, dataSource,
from, from,

7
apps/client/src/app/components/admin-market-data/admin-market-data.component.ts

@ -140,6 +140,11 @@ export class GfAdminMarketDataComponent
id: 'ETF_WITHOUT_SECTORS', id: 'ETF_WITHOUT_SECTORS',
label: $localize`ETFs without Sectors`, label: $localize`ETFs without Sectors`,
type: 'PRESET_ID' as Filter['type'] type: 'PRESET_ID' as Filter['type']
},
{
id: 'NO_ACTIVITIES',
label: $localize`No Activities`,
type: 'PRESET_ID' as Filter['type']
} }
]; ];
public benchmarks: Partial<SymbolProfile>[]; public benchmarks: Partial<SymbolProfile>[];
@ -374,7 +379,7 @@ export class GfAdminMarketDataComponent
this.pageSize = this.pageSize =
this.activeFilters.length === 1 && this.activeFilters.length === 1 &&
this.activeFilters[0].type === 'PRESET_ID' this.activeFilters[0].type === 'PRESET_ID'
? undefined ? Number.MAX_SAFE_INTEGER
: DEFAULT_PAGE_SIZE; : DEFAULT_PAGE_SIZE;
if (pageIndex === 0 && this.paginator) { if (pageIndex === 0 && this.paginator) {

7
apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html

@ -184,18 +184,21 @@
[ngClass]="{ [ngClass]="{
'cursor-pointer': 'cursor-pointer':
hasPermissionToUpdateUserSettings && hasPermissionToUpdateUserSettings &&
!user?.settings?.isRestrictedView &&
user?.subscription?.type !== 'Basic' user?.subscription?.type !== 'Basic'
}" }"
(click)=" (click)="
hasPermissionToUpdateUserSettings && hasPermissionToUpdateUserSettings &&
!user?.settings?.isRestrictedView &&
user?.subscription?.type !== 'Basic' && user?.subscription?.type !== 'Basic' &&
onEditEmergencyFund() onEditEmergencyFund()
" "
> >
@if ( @if (
hasPermissionToUpdateUserSettings && hasPermissionToUpdateUserSettings &&
user?.subscription?.type !== 'Basic' && !isLoading &&
!isLoading !user?.settings?.isRestrictedView &&
user?.subscription?.type !== 'Basic'
) { ) {
<ion-icon <ion-icon
class="mr-1 text-muted" class="mr-1 text-muted"

12
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 { MarkdownModule } from 'ngx-markdown';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { Subject } from 'rxjs';
@Component({ @Component({
imports: [MarkdownModule, NgxSkeletonLoaderModule], imports: [MarkdownModule, NgxSkeletonLoaderModule],
@ -9,17 +8,10 @@ import { Subject } from 'rxjs';
styleUrls: ['./changelog-page.scss'], styleUrls: ['./changelog-page.scss'],
templateUrl: './changelog-page.html' templateUrl: './changelog-page.html'
}) })
export class GfChangelogPageComponent implements OnDestroy { export class GfChangelogPageComponent {
public isLoading = true; public isLoading = true;
private unsubscribeSubject = new Subject<void>();
public onLoad() { public onLoad() {
this.isLoading = false; this.isLoading = false;
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
} }

12
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 { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { IonIcon } from '@ionic/angular/standalone'; import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { arrowForwardOutline } from 'ionicons/icons'; import { arrowForwardOutline } from 'ionicons/icons';
import { Subject } from 'rxjs';
const ossFriends = require('../../../../assets/oss-friends.json'); const ossFriends = require('../../../../assets/oss-friends.json');
@ -14,17 +13,10 @@ const ossFriends = require('../../../../assets/oss-friends.json');
styleUrls: ['./oss-friends-page.scss'], styleUrls: ['./oss-friends-page.scss'],
templateUrl: './oss-friends-page.html' templateUrl: './oss-friends-page.html'
}) })
export class GfOpenSourceSoftwareFriendsPageComponent implements OnDestroy { export class GfOpenSourceSoftwareFriendsPageComponent {
public ossFriends = ossFriends.data; public ossFriends = ossFriends.data;
private unsubscribeSubject = new Subject<void>();
public constructor() { public constructor() {
addIcons({ arrowForwardOutline }); addIcons({ arrowForwardOutline });
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
} }

12
apps/client/src/app/pages/admin/admin-page.component.ts

@ -1,7 +1,7 @@
import { TabConfiguration } from '@ghostfolio/common/interfaces'; import { TabConfiguration } from '@ghostfolio/common/interfaces';
import { internalRoutes } from '@ghostfolio/common/routes/routes'; 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 { MatTabsModule } from '@angular/material/tabs';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone'; import { IonIcon } from '@ionic/angular/standalone';
@ -14,7 +14,6 @@ import {
settingsOutline settingsOutline
} from 'ionicons/icons'; } from 'ionicons/icons';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs';
@Component({ @Component({
host: { class: 'page has-tabs' }, host: { class: 'page has-tabs' },
@ -23,12 +22,10 @@ import { Subject } from 'rxjs';
styleUrls: ['./admin-page.scss'], styleUrls: ['./admin-page.scss'],
templateUrl: './admin-page.html' templateUrl: './admin-page.html'
}) })
export class AdminPageComponent implements OnDestroy, OnInit { export class AdminPageComponent implements OnInit {
public deviceType: string; public deviceType: string;
public tabs: TabConfiguration[] = []; public tabs: TabConfiguration[] = [];
private unsubscribeSubject = new Subject<void>();
public constructor(private deviceService: DeviceDetectorService) { public constructor(private deviceService: DeviceDetectorService) {
addIcons({ addIcons({
flashOutline, flashOutline,
@ -74,9 +71,4 @@ export class AdminPageComponent implements OnDestroy, OnInit {
} }
]; ];
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
} }

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

@ -188,8 +188,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
!this.data.activity?.accountId && !this.data.activity?.accountId &&
this.mode === 'create' this.mode === 'create'
? this.data.accounts[0].id ? this.data.accounts[0].id
: this.data.activity?.accountId, : this.data.activity?.accountId
Validators.required
], ],
assetClass: [this.data.activity?.SymbolProfile?.assetClass], assetClass: [this.data.activity?.SymbolProfile?.assetClass],
assetSubClass: [this.data.activity?.SymbolProfile?.assetSubClass], assetSubClass: [this.data.activity?.SymbolProfile?.assetSubClass],
@ -365,11 +364,6 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
(this.activityForm.get('dataSource').value === 'MANUAL' && (this.activityForm.get('dataSource').value === 'MANUAL' &&
type === 'BUY') type === 'BUY')
) { ) {
this.activityForm
.get('accountId')
.removeValidators(Validators.required);
this.activityForm.get('accountId').updateValueAndValidity();
const currency = const currency =
this.data.accounts.find(({ id }) => { this.data.accounts.find(({ id }) => {
return id === this.activityForm.get('accountId').value; return id === this.activityForm.get('accountId').value;
@ -397,11 +391,6 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
this.activityForm.get('updateAccountBalance').disable(); this.activityForm.get('updateAccountBalance').disable();
this.activityForm.get('updateAccountBalance').setValue(false); this.activityForm.get('updateAccountBalance').setValue(false);
} else if (['FEE', 'INTEREST', 'LIABILITY'].includes(type)) { } else if (['FEE', 'INTEREST', 'LIABILITY'].includes(type)) {
this.activityForm
.get('accountId')
.removeValidators(Validators.required);
this.activityForm.get('accountId').updateValueAndValidity();
const currency = const currency =
this.data.accounts.find(({ id }) => { this.data.accounts.find(({ id }) => {
return id === this.activityForm.get('accountId').value; return id === this.activityForm.get('accountId').value;
@ -447,8 +436,6 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
this.activityForm.get('updateAccountBalance').setValue(false); this.activityForm.get('updateAccountBalance').setValue(false);
} }
} else { } else {
this.activityForm.get('accountId').setValidators(Validators.required);
this.activityForm.get('accountId').updateValueAndValidity();
this.activityForm this.activityForm
.get('dataSource') .get('dataSource')
.setValidators(Validators.required); .setValidators(Validators.required);

8
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html

@ -84,12 +84,8 @@
> >
<mat-label i18n>Account</mat-label> <mat-label i18n>Account</mat-label>
<mat-select formControlName="accountId"> <mat-select formControlName="accountId">
@if ( <mat-option [value]="null" />
!activityForm.get('accountId').hasValidator(Validators.required) ||
(!activityForm.get('accountId').value && mode === 'update')
) {
<mat-option [value]="null" />
}
@for (account of data.accounts; track account) { @for (account of data.accounts; track account) {
<mat-option [value]="account.id"> <mat-option [value]="account.id">
<div class="d-flex"> <div class="d-flex">

8
apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts

@ -9,6 +9,7 @@ import {
Component, Component,
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
Inject, Inject,
OnDestroy,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@ -52,7 +53,7 @@ import { UserAccountRegistrationDialogParams } from './interfaces/interfaces';
styleUrls: ['./user-account-registration-dialog.scss'], styleUrls: ['./user-account-registration-dialog.scss'],
templateUrl: 'user-account-registration-dialog.html' templateUrl: 'user-account-registration-dialog.html'
}) })
export class GfUserAccountRegistrationDialogComponent { export class GfUserAccountRegistrationDialogComponent implements OnDestroy {
@ViewChild(MatStepper) stepper!: MatStepper; @ViewChild(MatStepper) stepper!: MatStepper;
public accessToken: string; public accessToken: string;
@ -95,4 +96,9 @@ export class GfUserAccountRegistrationDialogComponent {
public onChangeDislaimerChecked() { public onChangeDislaimerChecked() {
this.isDisclaimerChecked = !this.isDisclaimerChecked; this.isDisclaimerChecked = !this.isDisclaimerChecked;
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
} }

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

@ -895,7 +895,7 @@
<target state="translated">Filtra per...</target> <target state="translated">Filtra per...</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">385</context> <context context-type="linenumber">390</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6182733719813772142" datatype="html"> <trans-unit id="6182733719813772142" datatype="html">
@ -935,7 +935,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6130372166370766747" datatype="html"> <trans-unit id="6130372166370766747" datatype="html">
@ -1003,7 +1003,7 @@
<target state="translated">Oooh! No s’han pogut recopilar les dades históriques.</target> <target state="translated">Oooh! No s’han pogut recopilar les dades históriques.</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context>
<context context-type="linenumber">262</context> <context context-type="linenumber">284</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4405333887341433096" datatype="html"> <trans-unit id="4405333887341433096" datatype="html">
@ -1035,7 +1035,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">71</context> <context context-type="linenumber">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5299488188278756127" datatype="html"> <trans-unit id="5299488188278756127" datatype="html">
@ -5924,6 +5924,14 @@
<context context-type="linenumber">27</context> <context context-type="linenumber">27</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9218541487912911620" datatype="html">
<source>No Activities</source>
<target state="new">No Activities</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="9219851060664514927" datatype="html"> <trans-unit id="9219851060664514927" datatype="html">
<source>Retirement Provision</source> <source>Retirement Provision</source>
<target state="translated">Provisió de jubilació</target> <target state="translated">Provisió de jubilació</target>
@ -6757,7 +6765,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context> <context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -7335,7 +7343,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">49</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7156797854368699223" datatype="html"> <trans-unit id="7156797854368699223" datatype="html">

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

@ -506,7 +506,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8122024350760043460" datatype="html"> <trans-unit id="8122024350760043460" datatype="html">
@ -2630,7 +2630,7 @@
<target state="translated">Filtern nach...</target> <target state="translated">Filtern nach...</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">385</context> <context context-type="linenumber">390</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1965206604774400" datatype="html"> <trans-unit id="1965206604774400" datatype="html">
@ -3234,7 +3234,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">71</context> <context context-type="linenumber">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4798457301875181136" datatype="html"> <trans-unit id="4798457301875181136" datatype="html">
@ -3357,6 +3357,14 @@
<context context-type="linenumber">22</context> <context context-type="linenumber">22</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9218541487912911620" datatype="html">
<source>No Activities</source>
<target state="translated">Keine Aktivitäten</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="9219851060664514927" datatype="html"> <trans-unit id="9219851060664514927" datatype="html">
<source>Retirement Provision</source> <source>Retirement Provision</source>
<target state="translated">Altersvorsorge</target> <target state="translated">Altersvorsorge</target>
@ -5716,7 +5724,7 @@
<target state="translated">Ups! Die historischen Daten konnten nicht geparsed werden.</target> <target state="translated">Ups! Die historischen Daten konnten nicht geparsed werden.</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context>
<context context-type="linenumber">262</context> <context context-type="linenumber">284</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="297546430113071258" datatype="html"> <trans-unit id="297546430113071258" datatype="html">
@ -6781,7 +6789,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context> <context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -7359,7 +7367,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">49</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7156797854368699223" datatype="html"> <trans-unit id="7156797854368699223" datatype="html">

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

@ -507,7 +507,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8122024350760043460" datatype="html"> <trans-unit id="8122024350760043460" datatype="html">
@ -2615,7 +2615,7 @@
<target state="translated">Filtrar por...</target> <target state="translated">Filtrar por...</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">385</context> <context context-type="linenumber">390</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5342721262799645301" datatype="html"> <trans-unit id="5342721262799645301" datatype="html">
@ -3219,7 +3219,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">71</context> <context context-type="linenumber">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4798457301875181136" datatype="html"> <trans-unit id="4798457301875181136" datatype="html">
@ -3342,6 +3342,14 @@
<context context-type="linenumber">22</context> <context context-type="linenumber">22</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9218541487912911620" datatype="html">
<source>No Activities</source>
<target state="new">No Activities</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="9219851060664514927" datatype="html"> <trans-unit id="9219851060664514927" datatype="html">
<source>Retirement Provision</source> <source>Retirement Provision</source>
<target state="translated">Provisión de jubilación</target> <target state="translated">Provisión de jubilación</target>
@ -5693,7 +5701,7 @@
<target state="translated">¡Ups! No se pudieron analizar los datos históricos.</target> <target state="translated">¡Ups! No se pudieron analizar los datos históricos.</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context>
<context context-type="linenumber">262</context> <context context-type="linenumber">284</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="297546430113071258" datatype="html"> <trans-unit id="297546430113071258" datatype="html">
@ -6758,7 +6766,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context> <context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -7336,7 +7344,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">49</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7156797854368699223" datatype="html"> <trans-unit id="7156797854368699223" datatype="html">
@ -7883,7 +7891,7 @@
</trans-unit> </trans-unit>
<trans-unit id="rule.feeRatioInitialInvestment" datatype="html"> <trans-unit id="rule.feeRatioInitialInvestment" datatype="html">
<source>Fee Ratio (legacy)</source> <source>Fee Ratio (legacy)</source>
<target state="new">Relación de tarifas</target> <target state="translated">Relación de tarifas (heredado)</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">152</context> <context context-type="linenumber">152</context>
@ -7907,7 +7915,7 @@
</trans-unit> </trans-unit>
<trans-unit id="rule.feeRatioTotalInvestmentVolume" datatype="html"> <trans-unit id="rule.feeRatioTotalInvestmentVolume" datatype="html">
<source>Fee Ratio</source> <source>Fee Ratio</source>
<target state="new">Fee Ratio</target> <target state="translated">Relación de tarifas</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">161</context> <context context-type="linenumber">161</context>
@ -7915,7 +7923,7 @@
</trans-unit> </trans-unit>
<trans-unit id="rule.feeRatioTotalInvestmentVolume.false" datatype="html"> <trans-unit id="rule.feeRatioTotalInvestmentVolume.false" datatype="html">
<source>The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%)</source> <source>The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%)</source>
<target state="new">The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%)</target> <target state="translated">Las tarifas superan el ${thresholdMax}% de su volumen total de inversión (${feeRatio}%)</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">163</context> <context context-type="linenumber">163</context>
@ -7923,7 +7931,7 @@
</trans-unit> </trans-unit>
<trans-unit id="rule.feeRatioTotalInvestmentVolume.true" datatype="html"> <trans-unit id="rule.feeRatioTotalInvestmentVolume.true" datatype="html">
<source>The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%)</source> <source>The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%)</source>
<target state="new">The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%)</target> <target state="translated">Las tarifas no superan el ${thresholdMax}% de su volumen total de inversión (${feeRatio}%)</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/i18n/i18n-page.html</context>
<context context-type="linenumber">167</context> <context context-type="linenumber">167</context>

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

@ -530,7 +530,7 @@
<target state="translated">Filtrer par...</target> <target state="translated">Filtrer par...</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">385</context> <context context-type="linenumber">390</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6182733719813772142" datatype="html"> <trans-unit id="6182733719813772142" datatype="html">
@ -570,7 +570,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6130372166370766747" datatype="html"> <trans-unit id="6130372166370766747" datatype="html">
@ -2222,7 +2222,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">71</context> <context context-type="linenumber">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2666668717343771434" datatype="html"> <trans-unit id="2666668717343771434" datatype="html">
@ -3341,6 +3341,14 @@
<context context-type="linenumber">22</context> <context context-type="linenumber">22</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9218541487912911620" datatype="html">
<source>No Activities</source>
<target state="new">No Activities</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="9219851060664514927" datatype="html"> <trans-unit id="9219851060664514927" datatype="html">
<source>Retirement Provision</source> <source>Retirement Provision</source>
<target state="translated">Réserve pour retraite</target> <target state="translated">Réserve pour retraite</target>
@ -5692,7 +5700,7 @@
<target state="translated">Oops! Echec du parsing des données historiques.</target> <target state="translated">Oops! Echec du parsing des données historiques.</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context>
<context context-type="linenumber">262</context> <context context-type="linenumber">284</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="297546430113071258" datatype="html"> <trans-unit id="297546430113071258" datatype="html">
@ -6757,7 +6765,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context> <context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -7335,7 +7343,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">49</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7156797854368699223" datatype="html"> <trans-unit id="7156797854368699223" datatype="html">

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

@ -507,7 +507,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8122024350760043460" datatype="html"> <trans-unit id="8122024350760043460" datatype="html">
@ -2615,7 +2615,7 @@
<target state="translated">Filtra per...</target> <target state="translated">Filtra per...</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">385</context> <context context-type="linenumber">390</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5342721262799645301" datatype="html"> <trans-unit id="5342721262799645301" datatype="html">
@ -3219,7 +3219,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">71</context> <context context-type="linenumber">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4798457301875181136" datatype="html"> <trans-unit id="4798457301875181136" datatype="html">
@ -3342,6 +3342,14 @@
<context context-type="linenumber">22</context> <context context-type="linenumber">22</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9218541487912911620" datatype="html">
<source>No Activities</source>
<target state="new">No Activities</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="9219851060664514927" datatype="html"> <trans-unit id="9219851060664514927" datatype="html">
<source>Retirement Provision</source> <source>Retirement Provision</source>
<target state="translated">Fondo pensione</target> <target state="translated">Fondo pensione</target>
@ -5693,7 +5701,7 @@
<target state="translated">Ops! Impossibile elaborare i dati storici.</target> <target state="translated">Ops! Impossibile elaborare i dati storici.</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context>
<context context-type="linenumber">262</context> <context context-type="linenumber">284</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="297546430113071258" datatype="html"> <trans-unit id="297546430113071258" datatype="html">
@ -6758,7 +6766,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context> <context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -7336,7 +7344,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">49</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7156797854368699223" datatype="html"> <trans-unit id="7156797854368699223" datatype="html">

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

@ -792,7 +792,7 @@
<target state="translated">다음 기준으로 필터...</target> <target state="translated">다음 기준으로 필터...</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">385</context> <context context-type="linenumber">390</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6182733719813772142" datatype="html"> <trans-unit id="6182733719813772142" datatype="html">
@ -832,7 +832,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6130372166370766747" datatype="html"> <trans-unit id="6130372166370766747" datatype="html">
@ -884,7 +884,7 @@
<target state="translated">이런! 과거 데이터를 파싱할 수 없습니다.</target> <target state="translated">이런! 과거 데이터를 파싱할 수 없습니다.</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context>
<context context-type="linenumber">262</context> <context context-type="linenumber">284</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1102717806459547726" datatype="html"> <trans-unit id="1102717806459547726" datatype="html">
@ -916,7 +916,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">71</context> <context context-type="linenumber">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5299488188278756127" datatype="html"> <trans-unit id="5299488188278756127" datatype="html">
@ -5376,6 +5376,14 @@
<context context-type="linenumber">27</context> <context context-type="linenumber">27</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9218541487912911620" datatype="html">
<source>No Activities</source>
<target state="new">No Activities</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="9219851060664514927" datatype="html"> <trans-unit id="9219851060664514927" datatype="html">
<source>Retirement Provision</source> <source>Retirement Provision</source>
<target state="translated">퇴직금</target> <target state="translated">퇴직금</target>
@ -6742,7 +6750,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context> <context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -7360,7 +7368,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">49</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1769610706135259386" datatype="html"> <trans-unit id="1769610706135259386" datatype="html">

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

@ -506,7 +506,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8122024350760043460" datatype="html"> <trans-unit id="8122024350760043460" datatype="html">
@ -2614,7 +2614,7 @@
<target state="translated">Filter op...</target> <target state="translated">Filter op...</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">385</context> <context context-type="linenumber">390</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5342721262799645301" datatype="html"> <trans-unit id="5342721262799645301" datatype="html">
@ -3218,7 +3218,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">71</context> <context context-type="linenumber">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4798457301875181136" datatype="html"> <trans-unit id="4798457301875181136" datatype="html">
@ -3341,6 +3341,14 @@
<context context-type="linenumber">22</context> <context context-type="linenumber">22</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9218541487912911620" datatype="html">
<source>No Activities</source>
<target state="new">No Activities</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="9219851060664514927" datatype="html"> <trans-unit id="9219851060664514927" datatype="html">
<source>Retirement Provision</source> <source>Retirement Provision</source>
<target state="translated">Pensioen</target> <target state="translated">Pensioen</target>
@ -5692,7 +5700,7 @@
<target state="translated">Oeps! Ophalen van historische data is mislukt.</target> <target state="translated">Oeps! Ophalen van historische data is mislukt.</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context>
<context context-type="linenumber">262</context> <context context-type="linenumber">284</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="297546430113071258" datatype="html"> <trans-unit id="297546430113071258" datatype="html">
@ -6757,7 +6765,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context> <context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -7335,7 +7343,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">49</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7156797854368699223" datatype="html"> <trans-unit id="7156797854368699223" datatype="html">

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

@ -783,7 +783,7 @@
<target state="translated">Filtruj według...</target> <target state="translated">Filtruj według...</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">385</context> <context context-type="linenumber">390</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6182733719813772142" datatype="html"> <trans-unit id="6182733719813772142" datatype="html">
@ -823,7 +823,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6130372166370766747" datatype="html"> <trans-unit id="6130372166370766747" datatype="html">
@ -859,7 +859,7 @@
<target state="translated">Ups! Nie udało się sparsować danych historycznych.</target> <target state="translated">Ups! Nie udało się sparsować danych historycznych.</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context>
<context context-type="linenumber">262</context> <context context-type="linenumber">284</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1102717806459547726" datatype="html"> <trans-unit id="1102717806459547726" datatype="html">
@ -883,7 +883,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">71</context> <context context-type="linenumber">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5299488188278756127" datatype="html"> <trans-unit id="5299488188278756127" datatype="html">
@ -5307,6 +5307,14 @@
<context context-type="linenumber">27</context> <context context-type="linenumber">27</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9218541487912911620" datatype="html">
<source>No Activities</source>
<target state="new">No Activities</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="9219851060664514927" datatype="html"> <trans-unit id="9219851060664514927" datatype="html">
<source>Retirement Provision</source> <source>Retirement Provision</source>
<target state="translated">Świadczenia Emerytalne</target> <target state="translated">Świadczenia Emerytalne</target>
@ -6757,7 +6765,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context> <context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -7335,7 +7343,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">49</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7156797854368699223" datatype="html"> <trans-unit id="7156797854368699223" datatype="html">

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

@ -530,7 +530,7 @@
<target state="translated">Filtrar por...</target> <target state="translated">Filtrar por...</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">385</context> <context context-type="linenumber">390</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6182733719813772142" datatype="html"> <trans-unit id="6182733719813772142" datatype="html">
@ -570,7 +570,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3720539089813177542" datatype="html"> <trans-unit id="3720539089813177542" datatype="html">
@ -3174,7 +3174,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">71</context> <context context-type="linenumber">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7763941937414903315" datatype="html"> <trans-unit id="7763941937414903315" datatype="html">
@ -3341,6 +3341,14 @@
<context context-type="linenumber">22</context> <context context-type="linenumber">22</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9218541487912911620" datatype="html">
<source>No Activities</source>
<target state="new">No Activities</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="9219851060664514927" datatype="html"> <trans-unit id="9219851060664514927" datatype="html">
<source>Retirement Provision</source> <source>Retirement Provision</source>
<target state="translated">Provisão de Reforma</target> <target state="translated">Provisão de Reforma</target>
@ -5692,7 +5700,7 @@
<target state="translated">Ops! Não foi possível analisar os dados históricos.</target> <target state="translated">Ops! Não foi possível analisar os dados históricos.</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context>
<context context-type="linenumber">262</context> <context context-type="linenumber">284</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="297546430113071258" datatype="html"> <trans-unit id="297546430113071258" datatype="html">
@ -6757,7 +6765,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context> <context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -7335,7 +7343,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">49</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7156797854368699223" datatype="html"> <trans-unit id="7156797854368699223" datatype="html">

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

@ -739,7 +739,7 @@
<target state="translated">Filtrele...</target> <target state="translated">Filtrele...</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">385</context> <context context-type="linenumber">390</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6182733719813772142" datatype="html"> <trans-unit id="6182733719813772142" datatype="html">
@ -779,7 +779,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6130372166370766747" datatype="html"> <trans-unit id="6130372166370766747" datatype="html">
@ -3371,7 +3371,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">71</context> <context context-type="linenumber">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2666668717343771434" datatype="html"> <trans-unit id="2666668717343771434" datatype="html">
@ -5003,6 +5003,14 @@
<context context-type="linenumber">27</context> <context context-type="linenumber">27</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9218541487912911620" datatype="html">
<source>No Activities</source>
<target state="new">No Activities</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="9219851060664514927" datatype="html"> <trans-unit id="9219851060664514927" datatype="html">
<source>Retirement Provision</source> <source>Retirement Provision</source>
<target state="translated">Yaşlılık Provizyonu</target> <target state="translated">Yaşlılık Provizyonu</target>
@ -5692,7 +5700,7 @@
<target state="translated">Hay Allah! Geçmiş veriler ayrıştırılamadı.</target> <target state="translated">Hay Allah! Geçmiş veriler ayrıştırılamadı.</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context>
<context context-type="linenumber">262</context> <context context-type="linenumber">284</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="297546430113071258" datatype="html"> <trans-unit id="297546430113071258" datatype="html">
@ -6757,7 +6765,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context> <context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -7335,7 +7343,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">49</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7156797854368699223" datatype="html"> <trans-unit id="7156797854368699223" datatype="html">

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

@ -875,7 +875,7 @@
<target state="translated">Фільтрувати за...</target> <target state="translated">Фільтрувати за...</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">385</context> <context context-type="linenumber">390</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8411428959611082933" datatype="html"> <trans-unit id="8411428959611082933" datatype="html">
@ -931,7 +931,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6130372166370766747" datatype="html"> <trans-unit id="6130372166370766747" datatype="html">
@ -2323,7 +2323,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">49</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5403336912114537863" datatype="html"> <trans-unit id="5403336912114537863" datatype="html">
@ -4576,7 +4576,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">71</context> <context context-type="linenumber">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2666668717343771434" datatype="html"> <trans-unit id="2666668717343771434" datatype="html">
@ -6419,7 +6419,7 @@
<target state="translated">Упс! Не вдалося отримати історичні дані.</target> <target state="translated">Упс! Не вдалося отримати історичні дані.</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context>
<context context-type="linenumber">262</context> <context context-type="linenumber">284</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="8927080808898221200" datatype="html"> <trans-unit id="8927080808898221200" datatype="html">
@ -6595,7 +6595,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context> <context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -6774,6 +6774,14 @@
<context context-type="linenumber">27</context> <context context-type="linenumber">27</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9218541487912911620" datatype="html">
<source>No Activities</source>
<target state="new">No Activities</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="9219851060664514927" datatype="html"> <trans-unit id="9219851060664514927" datatype="html">
<source>Retirement Provision</source> <source>Retirement Provision</source>
<target state="translated">Пенсійне накопичення</target> <target state="translated">Пенсійне накопичення</target>

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

@ -740,7 +740,7 @@
<source>Filter by...</source> <source>Filter by...</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">385</context> <context context-type="linenumber">390</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6182733719813772142" datatype="html"> <trans-unit id="6182733719813772142" datatype="html">
@ -777,7 +777,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6130372166370766747" datatype="html"> <trans-unit id="6130372166370766747" datatype="html">
@ -823,7 +823,7 @@
<source>Oops! Could not parse historical data.</source> <source>Oops! Could not parse historical data.</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context>
<context context-type="linenumber">262</context> <context context-type="linenumber">284</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1102717806459547726" datatype="html"> <trans-unit id="1102717806459547726" datatype="html">
@ -852,7 +852,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">71</context> <context context-type="linenumber">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5299488188278756127" datatype="html"> <trans-unit id="5299488188278756127" datatype="html">
@ -4907,6 +4907,13 @@
<context context-type="linenumber">27</context> <context context-type="linenumber">27</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9218541487912911620" datatype="html">
<source>No Activities</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="9219851060664514927" datatype="html"> <trans-unit id="9219851060664514927" datatype="html">
<source>Retirement Provision</source> <source>Retirement Provision</source>
<context-group purpose="location"> <context-group purpose="location">
@ -6126,7 +6133,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context> <context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -6685,7 +6692,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">49</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1769610706135259386" datatype="html"> <trans-unit id="1769610706135259386" datatype="html">

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

@ -792,7 +792,7 @@
<target state="translated">过滤...</target> <target state="translated">过滤...</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">385</context> <context context-type="linenumber">390</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6182733719813772142" datatype="html"> <trans-unit id="6182733719813772142" datatype="html">
@ -832,7 +832,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">44</context> <context context-type="linenumber">40</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="6130372166370766747" datatype="html"> <trans-unit id="6130372166370766747" datatype="html">
@ -868,7 +868,7 @@
<target state="translated">哎呀!无法解析历史数据。</target> <target state="translated">哎呀!无法解析历史数据。</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts</context>
<context context-type="linenumber">262</context> <context context-type="linenumber">284</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1102717806459547726" datatype="html"> <trans-unit id="1102717806459547726" datatype="html">
@ -892,7 +892,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html</context>
<context context-type="linenumber">71</context> <context context-type="linenumber">67</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5299488188278756127" datatype="html"> <trans-unit id="5299488188278756127" datatype="html">
@ -5360,6 +5360,14 @@
<context context-type="linenumber">27</context> <context context-type="linenumber">27</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="9218541487912911620" datatype="html">
<source>No Activities</source>
<target state="new">No Activities</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">146</context>
</context-group>
</trans-unit>
<trans-unit id="9219851060664514927" datatype="html"> <trans-unit id="9219851060664514927" datatype="html">
<source>Retirement Provision</source> <source>Retirement Provision</source>
<target state="translated">退休金</target> <target state="translated">退休金</target>
@ -6758,7 +6766,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">46</context> <context context-type="linenumber">47</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context> <context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
@ -7336,7 +7344,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context> <context context-type="sourcefile">libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">49</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="7156797854368699223" datatype="html"> <trans-unit id="7156797854368699223" datatype="html">

5
libs/common/src/lib/config.ts

@ -80,6 +80,11 @@ export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_TIMEOUT = 30000;
export const DEFAULT_REDACTED_PATHS = [ export const DEFAULT_REDACTED_PATHS = [
'accounts[*].balance', 'accounts[*].balance',
'accounts[*].balanceInBaseCurrency',
'accounts[*].comment',
'accounts[*].dividendInBaseCurrency',
'accounts[*].interestInBaseCurrency',
'accounts[*].value',
'accounts[*].valueInBaseCurrency', 'accounts[*].valueInBaseCurrency',
'activities[*].account.balance', 'activities[*].account.balance',
'activities[*].account.comment', 'activities[*].account.comment',

3
libs/common/src/lib/types/market-data-preset.type.ts

@ -2,4 +2,5 @@ export type MarketDataPreset =
| 'BENCHMARKS' | 'BENCHMARKS'
| 'CURRENCIES' | 'CURRENCIES'
| 'ETF_WITHOUT_COUNTRIES' | 'ETF_WITHOUT_COUNTRIES'
| 'ETF_WITHOUT_SECTORS'; | 'ETF_WITHOUT_SECTORS'
| 'NO_ACTIVITIES';

10
libs/ui/src/lib/account-balances/account-balances.component.html

@ -12,7 +12,7 @@
<ng-container i18n>Date</ng-container> <ng-container i18n>Date</ng-container>
</th> </th>
<td *matCellDef="let element" class="px-2" mat-cell> <td *matCellDef="let element" class="px-2" mat-cell>
<gf-value [isDate]="true" [locale]="locale" [value]="element?.date" /> <gf-value [isDate]="true" [locale]="locale()" [value]="element?.date" />
</td> </td>
<td *matFooterCellDef class="px-2" mat-footer-cell> <td *matFooterCellDef class="px-2" mat-footer-cell>
<mat-form-field appearance="outline" class="py-1 without-hint"> <mat-form-field appearance="outline" class="py-1 without-hint">
@ -37,7 +37,7 @@
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
<gf-value <gf-value
[isCurrency]="true" [isCurrency]="true"
[locale]="locale" [locale]="locale()"
[unit]="element?.account?.currency" [unit]="element?.account?.currency"
[value]="element?.value" [value]="element?.value"
/> />
@ -48,7 +48,7 @@
<mat-form-field appearance="outline" class="without-hint"> <mat-form-field appearance="outline" class="without-hint">
<input formControlName="balance" matInput type="number" /> <input formControlName="balance" matInput type="number" />
<div class="ml-2" matTextSuffix> <div class="ml-2" matTextSuffix>
{{ accountCurrency }} {{ accountCurrency() }}
</div> </div>
</mat-form-field> </mat-form-field>
</div> </div>
@ -58,7 +58,7 @@
<ng-container matColumnDef="actions" stickyEnd> <ng-container matColumnDef="actions" stickyEnd>
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell></th> <th *matHeaderCellDef class="px-1 text-center" mat-header-cell></th>
<td *matCellDef="let element" class="px-1 text-center" mat-cell> <td *matCellDef="let element" class="px-1 text-center" mat-cell>
@if (showActions) { @if (showActions()) {
<button <button
class="mx-1 no-min-width px-2" class="mx-1 no-min-width px-2"
mat-button mat-button
@ -100,7 +100,7 @@
<tr <tr
*matFooterRowDef="displayedColumns" *matFooterRowDef="displayedColumns"
mat-footer-row mat-footer-row
[hidden]="!showActions" [hidden]="!showActions()"
></tr> ></tr>
</table> </table>
</form> </form>

68
libs/ui/src/lib/account-balances/account-balances.component.ts

@ -10,12 +10,12 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
EventEmitter, EventEmitter,
Input,
OnChanges, OnChanges,
OnDestroy,
OnInit, OnInit,
Output, Output,
ViewChild inject,
input,
viewChild
} from '@angular/core'; } from '@angular/core';
import { import {
FormGroup, FormGroup,
@ -39,8 +39,7 @@ import {
ellipsisHorizontal, ellipsisHorizontal,
trashOutline trashOutline
} from 'ionicons/icons'; } from 'ionicons/icons';
import { get } from 'lodash'; import { get, isNil } from 'lodash';
import { Subject } from 'rxjs';
import { GfValueComponent } from '../value'; import { GfValueComponent } from '../value';
@ -63,50 +62,44 @@ import { GfValueComponent } from '../value';
styleUrls: ['./account-balances.component.scss'], styleUrls: ['./account-balances.component.scss'],
templateUrl: './account-balances.component.html' templateUrl: './account-balances.component.html'
}) })
export class GfAccountBalancesComponent export class GfAccountBalancesComponent implements OnChanges, OnInit {
implements OnChanges, OnDestroy, OnInit
{
@Input() accountBalances: AccountBalancesResponse['balances'];
@Input() accountCurrency: string;
@Input() accountId: string;
@Input() locale = getLocale();
@Input() showActions = true;
@Output() accountBalanceCreated = new EventEmitter<CreateAccountBalanceDto>(); @Output() accountBalanceCreated = new EventEmitter<CreateAccountBalanceDto>();
@Output() accountBalanceDeleted = new EventEmitter<string>(); @Output() accountBalanceDeleted = new EventEmitter<string>();
@ViewChild(MatSort) sort: MatSort; public readonly accountBalances =
input.required<AccountBalancesResponse['balances']>();
public readonly accountCurrency = input.required<string>();
public readonly accountId = input.required<string>();
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({ public accountBalanceForm = new FormGroup({
balance: new FormControl(0, Validators.required), balance: new FormControl(0, (control) => Validators.required(control)),
date: new FormControl(new Date(), Validators.required) date: new FormControl(new Date(), (control) => Validators.required(control))
}); });
public dataSource = new MatTableDataSource< public dataSource = new MatTableDataSource<
AccountBalancesResponse['balances'][0] AccountBalancesResponse['balances'][0]
>(); >();
public displayedColumns: string[] = ['date', 'value', 'actions']; private dateAdapter = inject<DateAdapter<Date, string>>(DateAdapter);
public Validators = Validators; private notificationService = inject(NotificationService);
private unsubscribeSubject = new Subject<void>();
public constructor( public constructor() {
private dateAdapter: DateAdapter<any>,
private notificationService: NotificationService
) {
addIcons({ calendarClearOutline, ellipsisHorizontal, trashOutline }); addIcons({ calendarClearOutline, ellipsisHorizontal, trashOutline });
} }
public ngOnInit() { public ngOnInit() {
this.dateAdapter.setLocale(this.locale); this.dateAdapter.setLocale(this.locale());
} }
public ngOnChanges() { public ngOnChanges() {
if (this.accountBalances) { if (this.accountBalances()) {
this.dataSource = new MatTableDataSource(this.accountBalances); this.dataSource = new MatTableDataSource(this.accountBalances());
this.dataSource.sort = this.sort; this.dataSource.sort = this.sort();
this.dataSource.sortingDataAccessor = get; this.dataSource.sortingDataAccessor = get;
} }
} }
@ -122,10 +115,16 @@ export class GfAccountBalancesComponent
} }
public async onSubmitAccountBalance() { public async onSubmitAccountBalance() {
const { balance, date } = this.accountBalanceForm.value;
if (isNil(balance) || !date) {
return;
}
const accountBalance: CreateAccountBalanceDto = { const accountBalance: CreateAccountBalanceDto = {
accountId: this.accountId, balance,
balance: this.accountBalanceForm.get('balance').value, accountId: this.accountId(),
date: format(this.accountBalanceForm.get('date').value, DATE_FORMAT) date: format(date, DATE_FORMAT)
}; };
try { try {
@ -141,9 +140,4 @@ export class GfAccountBalancesComponent
this.accountBalanceCreated.emit(accountBalance); this.accountBalanceCreated.emit(accountBalance);
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
} }

2
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' templateUrl: './accounts-table.component.html'
}) })
export class GfAccountsTableComponent { export class GfAccountsTableComponent {
public readonly accounts = input.required<Account[] | undefined>(); public readonly accounts = input.required<Account[]>();
public readonly activitiesCount = input<number>(); public readonly activitiesCount = input<number>();
public readonly baseCurrency = input<string>(); public readonly baseCurrency = input<string>();
public readonly hasPermissionToOpenDetails = input(true); public readonly hasPermissionToOpenDetails = input(true);

8
libs/ui/src/lib/activities-filter/activities-filter.component.html

@ -10,7 +10,7 @@
[removable]="true" [removable]="true"
(removed)="onRemoveFilter(filter)" (removed)="onRemoveFilter(filter)"
> >
{{ filter.label | gfSymbol }} {{ filter.label ?? '' | gfSymbol }}
<button matChipRemove> <button matChipRemove>
<ion-icon name="close-outline" /> <ion-icon name="close-outline" />
</button> </button>
@ -23,7 +23,7 @@
[matAutocomplete]="autocomplete" [matAutocomplete]="autocomplete"
[matChipInputFor]="chipList" [matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[placeholder]="placeholder" [placeholder]="placeholder()"
(matChipInputTokenEnd)="onAddFilter($event)" (matChipInputTokenEnd)="onAddFilter($event)"
/> />
</mat-chip-grid> </mat-chip-grid>
@ -35,7 +35,7 @@
<mat-optgroup [label]="filterGroup.name"> <mat-optgroup [label]="filterGroup.name">
@for (filter of filterGroup.filters; track filter) { @for (filter of filterGroup.filters; track filter) {
<mat-option [value]="filter.id"> <mat-option [value]="filter.id">
{{ filter.label | gfSymbol }} {{ filter.label ?? '' | gfSymbol }}
</mat-option> </mat-option>
} }
</mat-optgroup> </mat-optgroup>
@ -46,7 +46,7 @@
disabled disabled
mat-icon-button mat-icon-button
matSuffix matSuffix
[ngClass]="{ 'd-none': !isLoading }" [ngClass]="{ 'd-none': !isLoading() }"
> >
<mat-spinner matSuffix [diameter]="20" /> <mat-spinner matSuffix [diameter]="20" />
</button> </button>

83
libs/ui/src/lib/activities-filter/activities-filter.component.ts

@ -8,14 +8,14 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
ElementRef, ElementRef,
EventEmitter,
Input, Input,
OnChanges, OnChanges,
OnDestroy,
Output,
SimpleChanges, SimpleChanges,
ViewChild ViewChild,
input,
output
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { import {
MatAutocomplete, MatAutocomplete,
@ -30,8 +30,7 @@ import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { closeOutline, searchOutline } from 'ionicons/icons'; import { closeOutline, searchOutline } from 'ionicons/icons';
import { groupBy } from 'lodash'; import { groupBy } from 'lodash';
import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { translate } from '../i18n'; import { translate } from '../i18n';
@ -53,28 +52,26 @@ import { translate } from '../i18n';
styleUrls: ['./activities-filter.component.scss'], styleUrls: ['./activities-filter.component.scss'],
templateUrl: './activities-filter.component.html' templateUrl: './activities-filter.component.html'
}) })
export class GfActivitiesFilterComponent implements OnChanges, OnDestroy { export class GfActivitiesFilterComponent implements OnChanges {
@Input() allFilters: Filter[]; @Input() allFilters: Filter[];
@Input() isLoading: boolean;
@Input() placeholder: string;
@Output() valueChanged = new EventEmitter<Filter[]>(); @ViewChild('autocomplete') protected matAutocomplete: MatAutocomplete;
@ViewChild('searchInput') protected searchInput: ElementRef<HTMLInputElement>;
@ViewChild('autocomplete') matAutocomplete: MatAutocomplete; public readonly isLoading = input.required<boolean>();
@ViewChild('searchInput') searchInput: ElementRef<HTMLInputElement>; public readonly placeholder = input.required<string>();
public readonly valueChanged = output<Filter[]>();
public filterGroups$: Subject<FilterGroup[]> = new BehaviorSubject([]); protected readonly filterGroups$ = new BehaviorSubject<FilterGroup[]>([]);
public filters$: Subject<Filter[]> = new BehaviorSubject([]); protected readonly searchControl = new FormControl<Filter | string | null>(
public filters: Observable<Filter[]> = this.filters$.asObservable(); null
public searchControl = new FormControl<Filter | string>(undefined); );
public selectedFilters: Filter[] = []; protected selectedFilters: Filter[] = [];
public separatorKeysCodes: number[] = [ENTER, COMMA]; protected readonly separatorKeysCodes: number[] = [ENTER, COMMA];
private unsubscribeSubject = new Subject<void>();
public constructor() { public constructor() {
this.searchControl.valueChanges this.searchControl.valueChanges
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed())
.subscribe((filterOrSearchTerm) => { .subscribe((filterOrSearchTerm) => {
if (filterOrSearchTerm) { if (filterOrSearchTerm) {
const searchTerm = const searchTerm =
@ -97,41 +94,39 @@ export class GfActivitiesFilterComponent implements OnChanges, OnDestroy {
} }
} }
public onAddFilter({ input, value }: MatChipInputEvent) { public onAddFilter({ chipInput, value }: MatChipInputEvent) {
if (value?.trim()) { if (value?.trim()) {
this.updateFilters(); this.updateFilters();
} }
// Reset the input value // Reset the input value
if (input) { if (chipInput.inputElement) {
input.value = ''; chipInput.inputElement.value = '';
} }
this.searchControl.setValue(undefined); this.searchControl.setValue(null);
} }
public onRemoveFilter(aFilter: Filter) { public onRemoveFilter(aFilter: Filter) {
this.selectedFilters = this.selectedFilters.filter((filter) => { this.selectedFilters = this.selectedFilters.filter(({ id }) => {
return filter.id !== aFilter.id; return id !== aFilter.id;
}); });
this.updateFilters(); this.updateFilters();
} }
public onSelectFilter(event: MatAutocompleteSelectedEvent) { public onSelectFilter(event: MatAutocompleteSelectedEvent) {
this.selectedFilters.push( const filter = this.allFilters.find(({ id }) => {
this.allFilters.find((filter) => { return id === event.option.value;
return filter.id === event.option.value; });
})
); if (filter) {
this.selectedFilters.push(filter);
}
this.updateFilters(); this.updateFilters();
this.searchInput.nativeElement.value = ''; this.searchInput.nativeElement.value = '';
this.searchControl.setValue(undefined); this.searchControl.setValue(null);
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
} }
private getGroupedFilters(searchTerm?: string) { private getGroupedFilters(searchTerm?: string) {
@ -139,23 +134,23 @@ export class GfActivitiesFilterComponent implements OnChanges, OnDestroy {
this.allFilters this.allFilters
.filter((filter) => { .filter((filter) => {
// Filter selected filters // Filter selected filters
return !this.selectedFilters.some((selectedFilter) => { return !this.selectedFilters.some(({ id }) => {
return selectedFilter.id === filter.id; return id === filter.id;
}); });
}) })
.filter((filter) => { .filter((filter) => {
if (searchTerm) { if (searchTerm) {
// Filter by search term // Filter by search term
return filter.label return filter.label
.toLowerCase() ?.toLowerCase()
.includes(searchTerm.toLowerCase()); .includes(searchTerm.toLowerCase());
} }
return filter; return filter;
}) })
.sort((a, b) => a.label?.localeCompare(b.label)), .sort((a, b) => (a.label ?? '').localeCompare(b.label ?? '')),
(filter) => { ({ type }) => {
return filter.type; return type;
} }
); );

47
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, ChangeDetectorRef,
Component, Component,
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
Inject, DestroyRef,
OnDestroy, OnInit,
OnInit inject,
signal
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
@ -23,7 +25,6 @@ import { MatInputModule } from '@angular/material/input';
import { IonIcon } from '@ionic/angular/standalone'; import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { calendarClearOutline, refreshOutline } from 'ionicons/icons'; import { calendarClearOutline, refreshOutline } from 'ionicons/icons';
import { Subject, takeUntil } from 'rxjs';
import { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces'; import { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces';
@ -45,26 +46,27 @@ import { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces'
styleUrls: ['./historical-market-data-editor-dialog.scss'], styleUrls: ['./historical-market-data-editor-dialog.scss'],
templateUrl: 'historical-market-data-editor-dialog.html' templateUrl: 'historical-market-data-editor-dialog.html'
}) })
export class GfHistoricalMarketDataEditorDialogComponent export class GfHistoricalMarketDataEditorDialogComponent implements OnInit {
implements OnDestroy, OnInit public readonly data =
{ inject<HistoricalMarketDataEditorDialogParams>(MAT_DIALOG_DATA);
private unsubscribeSubject = new Subject<void>();
protected readonly marketPrice = signal(this.data.marketPrice);
private readonly destroyRef = inject(DestroyRef);
private readonly locale =
this.data.user.settings.locale ?? inject<string>(MAT_DATE_LOCALE);
public constructor( public constructor(
private adminService: AdminService, private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
@Inject(MAT_DIALOG_DATA)
public data: HistoricalMarketDataEditorDialogParams,
private dataService: DataService, private dataService: DataService,
private dateAdapter: DateAdapter<any>, private dateAdapter: DateAdapter<Date, string>,
public dialogRef: MatDialogRef<GfHistoricalMarketDataEditorDialogComponent>, public dialogRef: MatDialogRef<GfHistoricalMarketDataEditorDialogComponent>
@Inject(MAT_DATE_LOCALE) private locale: string
) { ) {
addIcons({ calendarClearOutline, refreshOutline }); addIcons({ calendarClearOutline, refreshOutline });
} }
public ngOnInit() { public ngOnInit() {
this.locale = this.data.user?.settings?.locale;
this.dateAdapter.setLocale(this.locale); this.dateAdapter.setLocale(this.locale);
} }
@ -79,15 +81,19 @@ export class GfHistoricalMarketDataEditorDialogComponent
dateString: this.data.dateString, dateString: this.data.dateString,
symbol: this.data.symbol symbol: this.data.symbol
}) })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ marketPrice }) => { .subscribe(({ marketPrice }) => {
this.data.marketPrice = marketPrice; this.marketPrice.set(marketPrice);
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
} }
public onUpdate() { public onUpdate() {
if (this.marketPrice() === undefined) {
return;
}
this.dataService this.dataService
.postMarketData({ .postMarketData({
dataSource: this.data.dataSource, dataSource: this.data.dataSource,
@ -95,20 +101,15 @@ export class GfHistoricalMarketDataEditorDialogComponent
marketData: [ marketData: [
{ {
date: this.data.dateString, date: this.data.dateString,
marketPrice: this.data.marketPrice marketPrice: this.marketPrice()
} }
] ]
}, },
symbol: this.data.symbol symbol: this.data.symbol
}) })
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => { .subscribe(() => {
this.dialogRef.close({ withRefresh: true }); this.dialogRef.close({ withRefresh: true });
}); });
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
} }

3
libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html

@ -28,7 +28,8 @@
matInput matInput
name="marketPrice" name="marketPrice"
type="number" type="number"
[(ngModel)]="data.marketPrice" [ngModel]="marketPrice()"
(ngModelChange)="marketPrice.set($event)"
/> />
<span class="ml-2" matTextSuffix>{{ data.currency }}</span> <span class="ml-2" matTextSuffix>{{ data.currency }}</span>
</mat-form-field> </mat-form-field>

2
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; currency: string;
dataSource: DataSource; dataSource: DataSource;
dateString: string; dateString: string;
marketPrice: number; marketPrice?: number;
symbol: string; symbol: string;
user: User; user: User;
} }

24
libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html

@ -3,28 +3,24 @@
<div class="d-flex"> <div class="d-flex">
<div class="date mr-1 text-nowrap">{{ itemByMonth.key }}</div> <div class="date mr-1 text-nowrap">{{ itemByMonth.key }}</div>
<div class="align-items-center d-flex flex-grow-1 px-1"> <div class="align-items-center d-flex flex-grow-1 px-1">
@for (dayItem of days; track dayItem; let i = $index) { @for (day of days; track day) {
<div <div
class="day" class="day"
[ngClass]="{ [ngClass]="{
'cursor-pointer valid': isDateOfInterest( 'cursor-pointer valid': isDateOfInterest(
`${itemByMonth.key}-${i + 1 < 10 ? `0${i + 1}` : i + 1}` `${itemByMonth.key}-${formatDay(day)}`
), ),
available: available:
marketDataByMonth[itemByMonth.key][ marketDataByMonth[itemByMonth.key][formatDay(day)]?.marketPrice,
i + 1 < 10 ? `0${i + 1}` : i + 1 today: isToday(`${itemByMonth.key}-${formatDay(day)}`)
]?.marketPrice,
today: isToday(
`${itemByMonth.key}-${i + 1 < 10 ? `0${i + 1}` : i + 1}`
)
}" }"
[title]=" [title]="
(`${itemByMonth.key}-${i + 1 < 10 ? `0${i + 1}` : i + 1}` (`${itemByMonth.key}-${formatDay(day)}`
| date: defaultDateFormat) ?? '' | date: defaultDateFormat()) ?? ''
" "
(click)=" (click)="
onOpenMarketDataDetail({ onOpenMarketDataDetail({
day: i + 1 < 10 ? `0${i + 1}` : i + 1, day: formatDay(day),
yearMonth: itemByMonth.key yearMonth: itemByMonth.key
}) })
" "
@ -61,10 +57,10 @@
mat-flat-button mat-flat-button
type="button" type="button"
[disabled]=" [disabled]="
!historicalDataForm.controls['historicalData']?.controls['csvString'] !historicalDataForm.controls.historicalData.controls.csvString
.touched || .touched ||
historicalDataForm.controls['historicalData']?.controls['csvString'] historicalDataForm.controls.historicalData.controls.csvString
?.value === '' .value === ''
" "
(click)="onImportHistoricalData()" (click)="onImportHistoricalData()"
> >

60
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<GfHistoricalMarketDataEditorComponent>;
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');
});
});
});

133
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 { DataService } from '@ghostfolio/ui/services';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import type { HttpErrorResponse } from '@angular/common/http';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
computed,
DestroyRef,
EventEmitter, EventEmitter,
inject,
input,
Input, Input,
OnChanges, OnChanges,
OnDestroy,
OnInit, OnInit,
Output Output
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
@ -40,7 +45,7 @@ import { first, last } from 'lodash';
import ms from 'ms'; import ms from 'ms';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { parse as csvToJson } from 'papaparse'; import { parse as csvToJson } from 'papaparse';
import { EMPTY, Subject, takeUntil } from 'rxjs'; import { EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators'; import { catchError } from 'rxjs/operators';
import { GfHistoricalMarketDataEditorDialogComponent } from './historical-market-data-editor-dialog/historical-market-data-editor-dialog.component'; 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' templateUrl: './historical-market-data-editor.component.html'
}) })
export class GfHistoricalMarketDataEditorComponent 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() currency: string;
@Input() dataSource: DataSource; @Input() dataSource: DataSource;
@Input() dateOfFirstActivity: string; @Input() dateOfFirstActivity: string;
@Input() locale = getLocale();
@Input() marketData: MarketData[];
@Input() symbol: string; @Input() symbol: string;
@Input() user: User; @Input() user: User;
@Output() marketDataChanged = new EventEmitter<boolean>(); @Output() marketDataChanged = new EventEmitter<boolean>();
public days = Array(31);
public defaultDateFormat: string;
public deviceType: string;
public historicalDataForm = this.formBuilder.group({ public historicalDataForm = this.formBuilder.group({
historicalData: this.formBuilder.group({ historicalData: this.formBuilder.group({
csvString: '' csvString: ''
}) })
}); });
public historicalDataItems: LineChartItem[];
public marketDataByMonth: { public marketDataByMonth: {
[yearMonth: string]: { [yearMonth: string]: {
[day: string]: Pick<MarketData, 'date' | 'marketPrice'> & { day: number }; [day: string]: {
date: Date;
day: number;
marketPrice?: number;
};
}; };
} = {}; } = {};
private static readonly HISTORICAL_DATA_TEMPLATE = `date;marketPrice\n${format( public readonly locale = input(getLocale());
new Date(), public readonly marketData = input.required<MarketData[]>();
DATE_FORMAT
)};123.45`; protected readonly days = Array.from({ length: 31 }, (_, i) => i + 1);
protected readonly defaultDateFormat = computed(() =>
private unsubscribeSubject = new Subject<void>(); 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<LineChartItem[]>(() =>
this.marketData().map(({ date, marketPrice }) => {
return {
date: format(date, DATE_FORMAT),
value: marketPrice
};
})
);
public constructor( public constructor(
private dataService: DataService, private dataService: DataService,
private deviceService: DeviceDetectorService,
private dialog: MatDialog, private dialog: MatDialog,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private snackBar: MatSnackBar private snackBar: MatSnackBar
) { ) {}
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
}
public ngOnInit() { public ngOnInit() {
this.initializeHistoricalDataForm(); this.initializeHistoricalDataForm();
} }
public ngOnChanges() { 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) { if (this.dateOfFirstActivity) {
let date = parseISO(this.dateOfFirstActivity); let date = parseISO(this.dateOfFirstActivity);
const missingMarketData: Partial<MarketData>[] = []; const missingMarketData: { date: Date; marketPrice?: number }[] = [];
if (this.historicalDataItems?.[0]?.date) { if (this.historicalDataItems()?.[0]?.date) {
while ( while (
isBefore( isBefore(
date, date,
parse(this.historicalDataItems[0].date, DATE_FORMAT, new Date()) parse(this.historicalDataItems()[0].date, DATE_FORMAT, new Date())
) )
) { ) {
missingMarketData.push({ 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() }); marketDataItems.push({ date: new Date() });
} }
@ -160,25 +172,34 @@ export class GfHistoricalMarketDataEditorComponent
// Fill up missing months // Fill up missing months
const dates = Object.keys(this.marketDataByMonth).sort(); const dates = Object.keys(this.marketDataByMonth).sort();
const startDateString = first(dates);
const startDate = min([ const startDate = min([
parseISO(this.dateOfFirstActivity), 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)) { let currentDate = startDate;
const key = format(currentDate, 'yyyy-MM');
if (!this.marketDataByMonth[key]) {
this.marketDataByMonth[key] = {};
}
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) { public isDateOfInterest(aDateString: string) {
// Date is valid and in the past // Date is valid and in the past
const date = parse(aDateString, DATE_FORMAT, new Date()); const date = parse(aDateString, DATE_FORMAT, new Date());
@ -201,7 +222,8 @@ export class GfHistoricalMarketDataEditorComponent
const dialogRef = this.dialog.open< const dialogRef = this.dialog.open<
GfHistoricalMarketDataEditorDialogComponent, GfHistoricalMarketDataEditorDialogComponent,
HistoricalMarketDataEditorDialogParams HistoricalMarketDataEditorDialogParams,
{ withRefresh: boolean }
>(GfHistoricalMarketDataEditorDialogComponent, { >(GfHistoricalMarketDataEditorDialogComponent, {
data: { data: {
marketPrice, marketPrice,
@ -211,13 +233,13 @@ export class GfHistoricalMarketDataEditorComponent
symbol: this.symbol, symbol: this.symbol,
user: this.user user: this.user
}, },
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType() === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType() === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef dialogRef
.afterClosed() .afterClosed()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ withRefresh } = { withRefresh: false }) => { .subscribe(({ withRefresh } = { withRefresh: false }) => {
this.marketDataChanged.emit(withRefresh); this.marketDataChanged.emit(withRefresh);
}); });
@ -225,15 +247,15 @@ export class GfHistoricalMarketDataEditorComponent
public onImportHistoricalData() { public onImportHistoricalData() {
try { try {
const marketData = csvToJson( const marketData = csvToJson<UpdateMarketDataDto>(
this.historicalDataForm.controls['historicalData'].controls['csvString'] this.historicalDataForm.controls.historicalData.controls.csvString
.value, .value ?? '',
{ {
dynamicTyping: true, dynamicTyping: true,
header: true, header: true,
skipEmptyLines: true skipEmptyLines: true
} }
).data as UpdateMarketDataDto[]; ).data;
this.dataService this.dataService
.postMarketData({ .postMarketData({
@ -244,13 +266,13 @@ export class GfHistoricalMarketDataEditorComponent
symbol: this.symbol symbol: this.symbol
}) })
.pipe( .pipe(
catchError(({ error, message }) => { catchError(({ error, message }: HttpErrorResponse) => {
this.snackBar.open(`${error}: ${message[0]}`, undefined, { this.snackBar.open(`${error}: ${message[0]}`, undefined, {
duration: ms('3 seconds') duration: ms('3 seconds')
}); });
return EMPTY; return EMPTY;
}), }),
takeUntil(this.unsubscribeSubject) takeUntilDestroyed(this.destroyRef)
) )
.subscribe(() => { .subscribe(() => {
this.initializeHistoricalDataForm(); this.initializeHistoricalDataForm();
@ -268,11 +290,6 @@ export class GfHistoricalMarketDataEditorComponent
} }
} }
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private initializeHistoricalDataForm() { private initializeHistoricalDataForm() {
this.historicalDataForm.setValue({ this.historicalDataForm.setValue({
historicalData: { historicalData: {

12
package-lock.json

@ -1,12 +1,12 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.239.0", "version": "2.242.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.239.0", "version": "2.242.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
@ -70,7 +70,7 @@
"ionicons": "8.0.13", "ionicons": "8.0.13",
"jsonpath": "1.1.1", "jsonpath": "1.1.1",
"lodash": "4.17.23", "lodash": "4.17.23",
"marked": "17.0.1", "marked": "17.0.2",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
"ng-extract-i18n-merge": "3.2.1", "ng-extract-i18n-merge": "3.2.1",
"ngx-device-detector": "11.0.0", "ngx-device-detector": "11.0.0",
@ -25625,9 +25625,9 @@
} }
}, },
"node_modules/marked": { "node_modules/marked": {
"version": "17.0.1", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.2.tgz",
"integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", "integrity": "sha512-s5HZGFQea7Huv5zZcAGhJLT3qLpAfnY7v7GWkICUr0+Wd5TFEtdlRR2XUL5Gg+RH7u2Df595ifrxR03mBaw7gA==",
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"marked": "bin/marked.js" "marked": "bin/marked.js"

7
package.json

@ -1,6 +1,6 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.239.0", "version": "2.242.0",
"homepage": "https://ghostfol.io", "homepage": "https://ghostfol.io",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio", "repository": "https://github.com/ghostfolio/ghostfolio",
@ -43,10 +43,11 @@
"start:production": "npm run database:migrate && npm run database:seed && node main", "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:server": "nx run api:copy-assets && nx run api:serve --watch",
"start:storybook": "nx run ui:storybook", "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:api": "npx dotenv-cli -e .env.example -- nx test api",
"test:common": "npx dotenv-cli -e .env.example -- nx test common", "test:common": "npx dotenv-cli -e .env.example -- nx test common",
"test:single": "nx run api:test --test-file object.helper.spec.ts", "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", "ts-node": "ts-node",
"update": "nx migrate latest", "update": "nx migrate latest",
"watch:server": "nx run api:copy-assets && nx run api:build --watch", "watch:server": "nx run api:copy-assets && nx run api:build --watch",
@ -114,7 +115,7 @@
"ionicons": "8.0.13", "ionicons": "8.0.13",
"jsonpath": "1.1.1", "jsonpath": "1.1.1",
"lodash": "4.17.23", "lodash": "4.17.23",
"marked": "17.0.1", "marked": "17.0.2",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
"ng-extract-i18n-merge": "3.2.1", "ng-extract-i18n-merge": "3.2.1",
"ngx-device-detector": "11.0.0", "ngx-device-detector": "11.0.0",

Loading…
Cancel
Save