Browse Source

Merge branch 'ghostfolio:main' into feature/upgrade-nestjs-version11

pull/4270/merge^2
Haruka Kishida 2 months ago
committed by GitHub
parent
commit
d837ba6c32
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 15
      CHANGELOG.md
  2. 6
      apps/api/src/app/portfolio/portfolio.service.ts
  3. 7
      apps/api/src/app/user/user.service.ts
  4. 79
      apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts
  5. 10
      apps/api/src/services/data-provider/coingecko/coingecko.service.ts
  6. 33
      apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html
  7. 16
      apps/client/src/locales/messages.ca.xlf
  8. 16
      apps/client/src/locales/messages.de.xlf
  9. 16
      apps/client/src/locales/messages.es.xlf
  10. 16
      apps/client/src/locales/messages.fr.xlf
  11. 16
      apps/client/src/locales/messages.it.xlf
  12. 16
      apps/client/src/locales/messages.nl.xlf
  13. 16
      apps/client/src/locales/messages.pl.xlf
  14. 16
      apps/client/src/locales/messages.pt.xlf
  15. 16
      apps/client/src/locales/messages.tr.xlf
  16. 16
      apps/client/src/locales/messages.uk.xlf
  17. 15
      apps/client/src/locales/messages.xlf
  18. 16
      apps/client/src/locales/messages.zh.xlf
  19. 1
      libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts
  20. 10
      libs/common/src/lib/personal-finance-tools.ts
  21. 1
      libs/ui/.storybook/preview.js
  22. 7
      libs/ui/project.json
  23. 2
      libs/ui/src/lib/logo/logo.component.stories.ts
  24. 90
      libs/ui/src/lib/tags-selector/tags-selector.component.html
  25. 95
      libs/ui/src/lib/tags-selector/tags-selector.component.stories.ts
  26. 12
      libs/ui/src/lib/tags-selector/tags-selector.component.ts
  27. 4
      package-lock.json
  28. 2
      package.json

15
CHANGELOG.md

@ -9,13 +9,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Extended the tags selector component by a `readonly` attribute
- Extended the tags selector component to support creating custom tags
- Added global styles to the _Storybook_ setup
### Changed
- Improved the language localization for German (`de`)
## 2.138.0 - 2025-02-08
### Added
- Added a new static portfolio analysis rule: _Regional Market Cluster Risk_ (Emerging Markets)
- Added a new static portfolio analysis rule: _Regional Market Cluster Risk_ (Europe)
- Added a link to _Duck.ai_ to the _Copy AI prompt to clipboard_ action on the analysis page (experimental)
- Extracted the tags selector to a reusable component used in the create or update activity dialog and holding detail dialog
- Added stories for the tags selector component
### Changed
- Improved the caching of the portfolio snapshot in the portfolio calculator by expiring cache entries when a user changes tags in the holding detail dialog
- Improved the error handling in the _CoinGecko_ service
- Improved the language localization for German (`de`)
- Upgraded `svgmap` from version `2.6.0` to `2.12.2`

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

@ -15,6 +15,7 @@ import { EconomicMarketClusterRiskDevelopedMarkets } from '@ghostfolio/api/model
import { EconomicMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/emerging-markets';
import { EmergencyFundSetup } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-setup';
import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment';
import { RegionalMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/emerging-markets';
import { RegionalMarketClusterRiskEurope } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/europe';
import { RegionalMarketClusterRiskNorthAmerica } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/north-america';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
@ -1279,6 +1280,11 @@ export class PortfolioService {
summary.ordersCount > 0
? await this.rulesService.evaluate(
[
new RegionalMarketClusterRiskEmergingMarkets(
this.exchangeRateDataService,
marketsAdvancedTotalInBaseCurrency,
marketsAdvanced.emergingMarkets.valueInBaseCurrency
),
new RegionalMarketClusterRiskEurope(
this.exchangeRateDataService,
marketsAdvancedTotalInBaseCurrency,

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

@ -13,6 +13,7 @@ import { EconomicMarketClusterRiskDevelopedMarkets } from '@ghostfolio/api/model
import { EconomicMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/emerging-markets';
import { EmergencyFundSetup } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-setup';
import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment';
import { RegionalMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/emerging-markets';
import { RegionalMarketClusterRiskEurope } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/europe';
import { RegionalMarketClusterRiskNorthAmerica } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/north-america';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
@ -271,6 +272,12 @@ export class UserService {
undefined,
undefined
).getSettings(user.Settings.settings),
RegionalMarketClusterRiskEmergingMarkets:
new RegionalMarketClusterRiskEmergingMarkets(
undefined,
undefined,
undefined
).getSettings(user.Settings.settings),
RegionalMarketClusterRiskEurope: new RegionalMarketClusterRiskEurope(
undefined,
undefined,

79
apps/api/src/models/rules/regional-market-cluster-risk/emerging-markets.ts

@ -0,0 +1,79 @@
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { Settings } from './interfaces/rule-settings.interface';
export class RegionalMarketClusterRiskEmergingMarkets extends Rule<Settings> {
private currentValueInBaseCurrency: number;
private emergingMarketsValueInBaseCurrency: number;
public constructor(
protected exchangeRateDataService: ExchangeRateDataService,
currentValueInBaseCurrency: number,
emergingMarketsValueInBaseCurrency: number
) {
super(exchangeRateDataService, {
key: RegionalMarketClusterRiskEmergingMarkets.name,
name: 'Emerging Markets'
});
this.currentValueInBaseCurrency = currentValueInBaseCurrency;
this.emergingMarketsValueInBaseCurrency =
emergingMarketsValueInBaseCurrency;
}
public evaluate(ruleSettings: Settings) {
const emergingMarketsValueRatio = this.currentValueInBaseCurrency
? this.emergingMarketsValueInBaseCurrency /
this.currentValueInBaseCurrency
: 0;
if (emergingMarketsValueRatio > ruleSettings.thresholdMax) {
return {
evaluation: `The Emerging Markets contribution of your current investment (${(emergingMarketsValueRatio * 100).toPrecision(3)}%) exceeds ${(
ruleSettings.thresholdMax * 100
).toPrecision(3)}%`,
value: false
};
} else if (emergingMarketsValueRatio < ruleSettings.thresholdMin) {
return {
evaluation: `The Emerging Markets contribution of your current investment (${(emergingMarketsValueRatio * 100).toPrecision(3)}%) is below ${(
ruleSettings.thresholdMin * 100
).toPrecision(3)}%`,
value: false
};
}
return {
evaluation: `The Emerging Markets contribution of your current investment (${(emergingMarketsValueRatio * 100).toPrecision(3)}%) is within the range of ${(
ruleSettings.thresholdMin * 100
).toPrecision(
3
)}% and ${(ruleSettings.thresholdMax * 100).toPrecision(3)}%`,
value: true
};
}
public getConfiguration() {
return {
threshold: {
max: 1,
min: 0,
step: 0.01,
unit: '%'
},
thresholdMax: true,
thresholdMin: true
};
}
public getSettings({ baseCurrency, xRayRules }: UserSettings): Settings {
return {
baseCurrency,
isActive: xRayRules?.[this.getKey()]?.isActive ?? true,
thresholdMax: xRayRules?.[this.getKey()]?.thresholdMax ?? 0.12,
thresholdMin: xRayRules?.[this.getKey()]?.thresholdMin ?? 0.08
};
}
}

10
apps/api/src/services/data-provider/coingecko/coingecko.service.ts

@ -112,7 +112,7 @@ export class CoinGeckoService implements DataProviderInterface {
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
}> {
try {
const { prices } = await fetch(
const { error, prices, status } = await fetch(
`${
this.apiUrl
}/coins/${symbol}/market_chart/range?vs_currency=${DEFAULT_CURRENCY.toLowerCase()}&from=${getUnixTime(
@ -124,6 +124,14 @@ export class CoinGeckoService implements DataProviderInterface {
}
).then((res) => res.json());
if (error?.status) {
throw new Error(error.status.error_message);
}
if (status) {
throw new Error(status.error_message);
}
const result: {
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
} = {

33
apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html

@ -366,33 +366,12 @@
</mat-tab>
</mat-tab-group>
<div
class="row"
[ngClass]="{
'd-none': !data.hasPermissionToUpdateOrder
}"
>
<div class="col">
<gf-tags-selector
[tags]="activityForm.get('tags')?.value"
[tagsAvailable]="tagsAvailable"
(tagsChanged)="onTagsChanged($event)"
/>
</div>
</div>
@if (!data.hasPermissionToUpdateOrder && tagsAvailable?.length > 0) {
<div class="row">
<div class="col">
<div class="h5" i18n>Tags</div>
<mat-chip-listbox>
@for (tag of tags; track tag) {
<mat-chip-option disabled>{{ tag.name }}</mat-chip-option>
}
</mat-chip-listbox>
</div>
</div>
}
<gf-tags-selector
[readonly]="!data.hasPermissionToUpdateOrder"
[tags]="activityForm.get('tags')?.value"
[tagsAvailable]="tagsAvailable"
(tagsChanged)="onTagsChanged($event)"
/>
@if (
dataSource?.data.length > 0 &&

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

@ -2218,12 +2218,12 @@
<context context-type="linenumber">87</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">387</context>
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">2</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="de074268d7d7d759a6ca93af78aace60e16bb671" datatype="html">
@ -2639,7 +2639,7 @@
<target state="translated">Informar d’un Problema amb les Dades</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">406</context>
<context context-type="linenumber">385</context>
</context-group>
</trans-unit>
<trans-unit id="8204176479746810612" datatype="html">
@ -7748,6 +7748,14 @@
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="70a67e04629f6d412db0a12d51820b480788d795" datatype="html">
<source>Create</source>
<target state="new">Create</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

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

@ -1357,12 +1357,12 @@
<context context-type="linenumber">87</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">387</context>
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">2</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="43d544c2e88959f6c59cc4db419528fb0776bd6c" datatype="html">
@ -1370,7 +1370,7 @@
<target state="translated">Datenfehler melden</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">406</context>
<context context-type="linenumber">385</context>
</context-group>
</trans-unit>
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
@ -7748,6 +7748,14 @@
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="70a67e04629f6d412db0a12d51820b480788d795" datatype="html">
<source>Create</source>
<target state="translated">Erstelle</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

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

@ -1358,12 +1358,12 @@
<context context-type="linenumber">87</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">387</context>
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">2</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="43d544c2e88959f6c59cc4db419528fb0776bd6c" datatype="html">
@ -1371,7 +1371,7 @@
<target state="translated">Reporta un anomalía de los datos</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">406</context>
<context context-type="linenumber">385</context>
</context-group>
</trans-unit>
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
@ -7749,6 +7749,14 @@
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="70a67e04629f6d412db0a12d51820b480788d795" datatype="html">
<source>Create</source>
<target state="new">Create</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

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

@ -969,12 +969,12 @@
<context context-type="linenumber">87</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">387</context>
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">2</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="62f17fd50522539fd4c85854828db9d2e1c5330f" datatype="html">
@ -1718,7 +1718,7 @@
<target state="translated">Signaler une Erreur de Données</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">406</context>
<context context-type="linenumber">385</context>
</context-group>
</trans-unit>
<trans-unit id="6048892649018070225" datatype="html">
@ -7748,6 +7748,14 @@
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="70a67e04629f6d412db0a12d51820b480788d795" datatype="html">
<source>Create</source>
<target state="new">Create</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

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

@ -1358,12 +1358,12 @@
<context context-type="linenumber">87</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">387</context>
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">2</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="43d544c2e88959f6c59cc4db419528fb0776bd6c" datatype="html">
@ -1371,7 +1371,7 @@
<target state="translated">Segnala un’anomalia dei dati</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">406</context>
<context context-type="linenumber">385</context>
</context-group>
</trans-unit>
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
@ -7749,6 +7749,14 @@
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="70a67e04629f6d412db0a12d51820b480788d795" datatype="html">
<source>Create</source>
<target state="new">Create</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

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

@ -1357,12 +1357,12 @@
<context context-type="linenumber">87</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">387</context>
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">2</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="43d544c2e88959f6c59cc4db419528fb0776bd6c" datatype="html">
@ -1370,7 +1370,7 @@
<target state="translated">Gegevensstoring melden</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">406</context>
<context context-type="linenumber">385</context>
</context-group>
</trans-unit>
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
@ -7748,6 +7748,14 @@
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="70a67e04629f6d412db0a12d51820b480788d795" datatype="html">
<source>Create</source>
<target state="new">Create</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

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

@ -2046,12 +2046,12 @@
<context context-type="linenumber">87</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">387</context>
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">2</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="de074268d7d7d759a6ca93af78aace60e16bb671" datatype="html">
@ -2803,7 +2803,7 @@
<target state="translated">Zgłoś Błąd Danych</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">406</context>
<context context-type="linenumber">385</context>
</context-group>
</trans-unit>
<trans-unit id="2573979fd7d8602db44b7b4ad493428bc354d2f5" datatype="html">
@ -7748,6 +7748,14 @@
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="70a67e04629f6d412db0a12d51820b480788d795" datatype="html">
<source>Create</source>
<target state="new">Create</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

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

@ -1665,12 +1665,12 @@
<context context-type="linenumber">87</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">387</context>
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">2</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="43d544c2e88959f6c59cc4db419528fb0776bd6c" datatype="html">
@ -1678,7 +1678,7 @@
<target state="translated">Dados do Relatório com Problema</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">406</context>
<context context-type="linenumber">385</context>
</context-group>
</trans-unit>
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
@ -7748,6 +7748,14 @@
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="70a67e04629f6d412db0a12d51820b480788d795" datatype="html">
<source>Create</source>
<target state="new">Create</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

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

@ -1782,12 +1782,12 @@
<context context-type="linenumber">87</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">387</context>
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">2</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="62f17fd50522539fd4c85854828db9d2e1c5330f" datatype="html">
@ -2647,7 +2647,7 @@
<target state="translated">Rapor Veri Sorunu</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">406</context>
<context context-type="linenumber">385</context>
</context-group>
</trans-unit>
<trans-unit id="2573979fd7d8602db44b7b4ad493428bc354d2f5" datatype="html">
@ -7748,6 +7748,14 @@
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="70a67e04629f6d412db0a12d51820b480788d795" datatype="html">
<source>Create</source>
<target state="new">Create</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

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

@ -2258,12 +2258,12 @@
<context context-type="linenumber">87</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">387</context>
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">2</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="5649402767950535555" datatype="html">
@ -2767,7 +2767,7 @@
<target state="translated">Повідомити про збій даних</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">406</context>
<context context-type="linenumber">385</context>
</context-group>
</trans-unit>
<trans-unit id="8204176479746810612" datatype="html">
@ -7748,6 +7748,14 @@
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="70a67e04629f6d412db0a12d51820b480788d795" datatype="html">
<source>Create</source>
<target state="new">Create</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

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

@ -1943,12 +1943,12 @@
<context context-type="linenumber">87</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">387</context>
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">2</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="de074268d7d7d759a6ca93af78aace60e16bb671" datatype="html">
@ -2623,7 +2623,7 @@
<source>Report Data Glitch</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">406</context>
<context context-type="linenumber">385</context>
</context-group>
</trans-unit>
<trans-unit id="2573979fd7d8602db44b7b4ad493428bc354d2f5" datatype="html">
@ -7007,6 +7007,13 @@
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="70a67e04629f6d412db0a12d51820b480788d795" datatype="html">
<source>Create</source>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

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

@ -2063,12 +2063,12 @@
<context context-type="linenumber">87</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">387</context>
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">2</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="de074268d7d7d759a6ca93af78aace60e16bb671" datatype="html">
@ -2820,7 +2820,7 @@
<target state="translated">报告数据故障</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html</context>
<context context-type="linenumber">406</context>
<context context-type="linenumber">385</context>
</context-group>
</trans-unit>
<trans-unit id="2573979fd7d8602db44b7b4ad493428bc354d2f5" datatype="html">
@ -7749,6 +7749,14 @@
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="70a67e04629f6d412db0a12d51820b480788d795" datatype="html">
<source>Create</source>
<target state="new">Create</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/tags-selector/tags-selector.component.html</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
</body>
</file>
</xliff>

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

@ -9,6 +9,7 @@ export interface XRayRulesSettings {
EconomicMarketClusterRiskEmergingMarkets?: RuleSettings;
EmergencyFundSetup?: RuleSettings;
FeeRatioInitialInvestment?: RuleSettings;
RegionalMarketClusterRiskEmergingMarkets?: RuleSettings;
RegionalMarketClusterRiskEurope?: RuleSettings;
RegionalMarketClusterRiskNorthAmerica?: RuleSettings;
}

10
libs/common/src/lib/personal-finance-tools.ts

@ -202,6 +202,16 @@ export const personalFinanceTools: Product[] = [
pricingPerYear: '$100',
slogan: 'All your wealth, in one place.'
},
{
founded: 2018,
hasFreePlan: false,
hasSelfHostingAbility: false,
key: 'fey',
name: 'Fey',
origin: 'Canada',
pricingPerYear: '$300',
slogan: 'Make better investments.'
},
{
founded: 2023,
hasFreePlan: true,

1
libs/ui/.storybook/preview.js

@ -1 +0,0 @@
// import '!style-loader!css-loader!sass-loader!../../../apps/client/src/styles.scss';

7
libs/ui/project.json

@ -44,7 +44,12 @@
"outputDir": "dist/storybook/ui",
"configDir": "libs/ui/.storybook",
"browserTarget": "ui:build-storybook",
"compodoc": false
"compodoc": false,
"styles": [
"apps/client/src/assets/fonts/inter.css",
"apps/client/src/styles/theme.scss",
"apps/client/src/styles.scss"
]
},
"configurations": {
"ci": {

2
libs/ui/src/lib/logo/logo.component.stories.ts

@ -25,7 +25,7 @@ export const Large: Story = {
}
};
export const NoLabel: Story = {
export const WithoutLabel: Story = {
args: {
showLabel: false
}

90
libs/ui/src/lib/tags-selector/tags-selector.component.html

@ -1,32 +1,60 @@
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-label i18n>Tags</mat-label>
<mat-chip-grid #tagsChipList>
@for (tag of tagsSelected(); track tag.id) {
<mat-chip-row
matChipRemove
[removable]="true"
(removed)="onRemoveTag(tag)"
>
{{ tag.name }}
<ion-icon matChipTrailingIcon name="close-outline" />
</mat-chip-row>
<div class="row">
<div class="col">
@if (readonly) {
<div class="h5" i18n>Tags</div>
@if (tags?.length > 0) {
<mat-chip-listbox>
@for (tag of tags; track tag) {
<mat-chip-option disabled>{{ tag.name }}</mat-chip-option>
}
</mat-chip-listbox>
} @else {
<div>-</div>
}
} @else {
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-label i18n>Tags</mat-label>
<mat-chip-grid #tagsChipList>
@for (tag of tagsSelected(); track tag.id) {
<mat-chip-row
matChipRemove
[removable]="true"
(removed)="onRemoveTag(tag)"
>
{{ tag.name }}
<ion-icon matChipTrailingIcon name="close-outline" />
</mat-chip-row>
}
<input
#tagInput
[formControl]="tagInputControl"
[matAutocomplete]="autocompleteTags"
[matChipInputFor]="tagsChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
/>
</mat-chip-grid>
<mat-autocomplete
#autocompleteTags="matAutocomplete"
(optionSelected)="onAddTag($event)"
>
@for (tag of filteredOptions | async; track tag.id) {
<mat-option [value]="tag.id">
{{ tag.name }}
</mat-option>
}
@if (hasPermissionToCreateTags && tagInputControl.value) {
<mat-option [value]="tagInputControl.value.trim()">
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="add-circle-outline" />
<ng-container i18n>Create</ng-container> "{{
tagInputControl.value.trim()
}}"
</span>
</mat-option>
}
</mat-autocomplete>
</mat-form-field>
}
<input
#tagInput
[formControl]="tagInputControl"
[matAutocomplete]="autocompleteTags"
[matChipInputFor]="tagsChipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
/>
</mat-chip-grid>
<mat-autocomplete
#autocompleteTags="matAutocomplete"
(optionSelected)="onAddTag($event)"
>
@for (tag of filteredOptions | async; track tag.id) {
<mat-option [value]="tag.id">
{{ tag.name }}
</mat-option>
}
</mat-autocomplete>
</mat-form-field>
</div>
</div>

95
libs/ui/src/lib/tags-selector/tags-selector.component.stories.ts

@ -0,0 +1,95 @@
import { CommonModule } from '@angular/common';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { Meta, moduleMetadata, StoryObj } from '@storybook/angular';
import { GfTagsSelectorComponent } from './tags-selector.component';
export default {
title: 'Tags Selector',
component: GfTagsSelectorComponent,
decorators: [
moduleMetadata({
imports: [CommonModule, NoopAnimationsModule]
})
]
} as Meta<GfTagsSelectorComponent>;
type Story = StoryObj<GfTagsSelectorComponent>;
const OPTIONS = [
{
id: '3ef7e6d9-4598-4eb2-b0e8-00e61cfc0ea6',
name: 'Gambling',
userId: 'c6a71541-d0e3-4e22-ae83-b5e5611b6695'
},
{
id: 'EMERGENCY_FUND',
name: 'Emergency Fund',
userId: null
},
{
id: 'RETIREMENT_FUND',
name: 'Retirement Fund',
userId: null
}
];
export const Default: Story = {
args: {
tags: [
{
id: 'EMERGENCY_FUND',
name: 'Emergency Fund',
userId: null
}
],
tagsAvailable: OPTIONS
}
};
export const CreateCustomTags: Story = {
args: {
hasPermissionToCreateTags: true,
tags: [
{
id: 'EMERGENCY_FUND',
name: 'Emergency Fund',
userId: null
}
],
tagsAvailable: OPTIONS
}
};
export const Readonly: Story = {
args: {
readonly: true,
tags: [
{
id: 'EMERGENCY_FUND',
name: 'Emergency Fund',
userId: null
},
{
id: 'RETIREMENT_FUND',
name: 'Retirement Fund',
userId: null
}
],
tagsAvailable: OPTIONS
}
};
export const WithoutValue: Story = {
args: {
tags: [],
tagsAvailable: OPTIONS
}
};
export const WithoutOptions: Story = {
args: {
tags: [],
tagsAvailable: []
}
};

12
libs/ui/src/lib/tags-selector/tags-selector.component.ts

@ -42,6 +42,8 @@ import { BehaviorSubject, Subject, takeUntil } from 'rxjs';
templateUrl: 'tags-selector.component.html'
})
export class GfTagsSelectorComponent implements OnInit, OnChanges, OnDestroy {
@Input() hasPermissionToCreateTags = false;
@Input() readonly = false;
@Input() tags: Tag[];
@Input() tagsAvailable: Tag[];
@ -75,10 +77,18 @@ export class GfTagsSelectorComponent implements OnInit, OnChanges, OnDestroy {
}
public onAddTag(event: MatAutocompleteSelectedEvent) {
const tag = this.tagsAvailable.find(({ id }) => {
let tag = this.tagsAvailable.find(({ id }) => {
return id === event.option.value;
});
if (!tag && this.hasPermissionToCreateTags) {
tag = {
id: undefined,
name: event.option.value as string,
userId: null
};
}
this.tagsSelected.update((tags) => {
return [...(tags ?? []), tag];
});

4
package-lock.json

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

2
package.json

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

Loading…
Cancel
Save