Browse Source

Merge remote-tracking branch 'origin/main' into feature/extend-holdings-endpoint-for-cash

pull/5650/head
KenTandrian 1 week ago
parent
commit
b2efcced89
  1. 14
      CHANGELOG.md
  2. 32
      apps/api/src/services/data-provider/coingecko/coingecko.service.ts
  3. 45
      apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts
  4. 87
      apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
  5. 34
      apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts
  6. 2
      apps/client/src/app/components/access-table/access-table.component.html
  7. 6
      apps/client/src/app/components/admin-jobs/admin-jobs.html
  8. 2
      apps/client/src/app/components/admin-market-data/admin-market-data.html
  9. 2
      apps/client/src/app/components/admin-platform/admin-platform.component.html
  10. 2
      apps/client/src/app/components/admin-tag/admin-tag.component.html
  11. 4
      apps/client/src/app/components/admin-users/admin-users.html
  12. 6
      libs/ui/src/lib/accounts-table/accounts-table.component.html
  13. 18
      libs/ui/src/lib/activities-table/activities-table.component.html
  14. 46
      libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.stories.ts
  15. 20
      package-lock.json
  16. 6
      package.json

14
CHANGELOG.md

@ -13,10 +13,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Refactored the API query parameters in various data provider services
- Extended the _Storybook_ stories of the portfolio proportion chart component by a story using percentage values
- Upgraded `@internationalized/number` from version `3.6.3` to `3.6.5`
- Upgraded `prettier` from version `3.7.2` to `3.7.3`
### Fixed
- Improved the country weightings in the _Financial Modeling Prep_ service
## 2.220.0 - 2025-11-29
### Changed
- Restricted the asset profile data gathering on Sundays to only process outdated asset profiles - Restricted the asset profile data gathering on Sundays to only process outdated asset profiles
- Removed the _Cypress_ testing setup - Removed the _Cypress_ testing setup
- Eliminated `uuid` in favor of using `randomUUID` from `node:crypto` - Eliminated `uuid` in favor of using `randomUUID` from `node:crypto`
- Upgraded `color` from version `5.0.0` to `5.0.3` - Upgraded `color` from version `5.0.0` to `5.0.3`
- Upgraded `prettier` from version `3.6.2` to `3.7.2`
### Fixed ### Fixed

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

@ -110,12 +110,14 @@ export class CoinGeckoService implements DataProviderInterface {
[symbol: string]: { [date: string]: DataProviderHistoricalResponse }; [symbol: string]: { [date: string]: DataProviderHistoricalResponse };
}> { }> {
try { try {
const queryParams = new URLSearchParams({
from: getUnixTime(from).toString(),
to: getUnixTime(to).toString(),
vs_currency: DEFAULT_CURRENCY.toLowerCase()
});
const { error, prices, status } = await fetch( const { error, prices, status } = await fetch(
`${ `${this.apiUrl}/coins/${symbol}/market_chart/range?${queryParams.toString()}`,
this.apiUrl
}/coins/${symbol}/market_chart/range?vs_currency=${DEFAULT_CURRENCY.toLowerCase()}&from=${getUnixTime(
from
)}&to=${getUnixTime(to)}`,
{ {
headers: this.headers, headers: this.headers,
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
@ -172,10 +174,13 @@ export class CoinGeckoService implements DataProviderInterface {
} }
try { try {
const queryParams = new URLSearchParams({
ids: symbols.join(','),
vs_currencies: DEFAULT_CURRENCY.toLowerCase()
});
const quotes = await fetch( const quotes = await fetch(
`${this.apiUrl}/simple/price?ids=${symbols.join( `${this.apiUrl}/simple/price?${queryParams.toString()}`,
','
)}&vs_currencies=${DEFAULT_CURRENCY.toLowerCase()}`,
{ {
headers: this.headers, headers: this.headers,
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
@ -219,10 +224,17 @@ export class CoinGeckoService implements DataProviderInterface {
let items: LookupItem[] = []; let items: LookupItem[] = [];
try { try {
const { coins } = await fetch(`${this.apiUrl}/search?query=${query}`, { const queryParams = new URLSearchParams({
query
});
const { coins } = await fetch(
`${this.apiUrl}/search?${queryParams.toString()}`,
{
headers: this.headers, headers: this.headers,
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
}).then((res) => res.json()); }
).then((res) => res.json());
items = coins.map(({ id: symbol, name }) => { items = coins.map(({ id: symbol, name }) => {
return { return {

45
apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts

@ -96,17 +96,19 @@ export class EodHistoricalDataService implements DataProviderInterface {
} }
try { try {
const queryParams = new URLSearchParams({
api_token: this.apiKey,
fmt: 'json',
from: format(from, DATE_FORMAT),
to: format(to, DATE_FORMAT)
});
const response: { const response: {
[date: string]: DataProviderHistoricalResponse; [date: string]: DataProviderHistoricalResponse;
} = {}; } = {};
const historicalResult = await fetch( const historicalResult = await fetch(
`${this.URL}/div/${symbol}?api_token=${ `${this.URL}/div/${symbol}?${queryParams.toString()}`,
this.apiKey
}&fmt=json&from=${format(from, DATE_FORMAT)}&to=${format(
to,
DATE_FORMAT
)}`,
{ {
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }
@ -144,13 +146,16 @@ export class EodHistoricalDataService implements DataProviderInterface {
symbol = this.convertToEodSymbol(symbol); symbol = this.convertToEodSymbol(symbol);
try { try {
const queryParams = new URLSearchParams({
api_token: this.apiKey,
fmt: 'json',
from: format(from, DATE_FORMAT),
period: granularity,
to: format(to, DATE_FORMAT)
});
const response = await fetch( const response = await fetch(
`${this.URL}/eod/${symbol}?api_token=${ `${this.URL}/eod/${symbol}?${queryParams.toString()}`,
this.apiKey
}&fmt=json&from=${format(from, DATE_FORMAT)}&to=${format(
to,
DATE_FORMAT
)}&period=${granularity}`,
{ {
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }
@ -208,10 +213,14 @@ export class EodHistoricalDataService implements DataProviderInterface {
}); });
try { try {
const queryParams = new URLSearchParams({
api_token: this.apiKey,
fmt: 'json',
s: eodHistoricalDataSymbols.join(',')
});
const realTimeResponse = await fetch( const realTimeResponse = await fetch(
`${this.URL}/real-time/${eodHistoricalDataSymbols[0]}?api_token=${ `${this.URL}/real-time/${eodHistoricalDataSymbols[0]}?${queryParams.toString()}`,
this.apiKey
}&fmt=json&s=${eodHistoricalDataSymbols.join(',')}`,
{ {
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }
@ -413,8 +422,12 @@ export class EodHistoricalDataService implements DataProviderInterface {
})[] = []; })[] = [];
try { try {
const queryParams = new URLSearchParams({
api_token: this.apiKey
});
const response = await fetch( const response = await fetch(
`${this.URL}/search/${query}?api_token=${this.apiKey}`, `${this.URL}/search/${query}?${queryParams.toString()}`,
{ {
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }

87
apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts

@ -44,6 +44,12 @@ import {
@Injectable() @Injectable()
export class FinancialModelingPrepService implements DataProviderInterface { export class FinancialModelingPrepService implements DataProviderInterface {
private static countriesMapping = {
'Korea (the Republic of)': 'South Korea',
'Russian Federation': 'Russia',
'Taiwan (Province of China)': 'Taiwan'
};
private apiKey: string; private apiKey: string;
public constructor( public constructor(
@ -79,8 +85,13 @@ export class FinancialModelingPrepService implements DataProviderInterface {
symbol.length - DEFAULT_CURRENCY.length symbol.length - DEFAULT_CURRENCY.length
); );
} else if (this.cryptocurrencyService.isCryptocurrency(symbol)) { } else if (this.cryptocurrencyService.isCryptocurrency(symbol)) {
const queryParams = new URLSearchParams({
symbol,
apikey: this.apiKey
});
const [quote] = await fetch( const [quote] = await fetch(
`${this.getUrl({ version: 'stable' })}/quote?symbol=${symbol}&apikey=${this.apiKey}`, `${this.getUrl({ version: 'stable' })}/quote?${queryParams.toString()}`,
{ {
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }
@ -93,8 +104,13 @@ export class FinancialModelingPrepService implements DataProviderInterface {
); );
response.name = quote.name; response.name = quote.name;
} else { } else {
const queryParams = new URLSearchParams({
symbol,
apikey: this.apiKey
});
const [assetProfile] = await fetch( const [assetProfile] = await fetch(
`${this.getUrl({ version: 'stable' })}/profile?symbol=${symbol}&apikey=${this.apiKey}`, `${this.getUrl({ version: 'stable' })}/profile?${queryParams.toString()}`,
{ {
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }
@ -114,19 +130,31 @@ export class FinancialModelingPrepService implements DataProviderInterface {
assetSubClass === AssetSubClass.ETF || assetSubClass === AssetSubClass.ETF ||
assetSubClass === AssetSubClass.MUTUALFUND assetSubClass === AssetSubClass.MUTUALFUND
) { ) {
const queryParams = new URLSearchParams({
symbol,
apikey: this.apiKey
});
const etfCountryWeightings = await fetch( const etfCountryWeightings = await fetch(
`${this.getUrl({ version: 'stable' })}/etf/country-weightings?symbol=${symbol}&apikey=${this.apiKey}`, `${this.getUrl({ version: 'stable' })}/etf/country-weightings?${queryParams.toString()}`,
{ {
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }
).then((res) => res.json()); ).then((res) => res.json());
response.countries = etfCountryWeightings.map( response.countries = etfCountryWeightings
({ country: countryName, weightPercentage }) => { .filter(({ country: countryName }) => {
return countryName.toLowerCase() !== 'other';
})
.map(({ country: countryName, weightPercentage }) => {
let countryCode: string; let countryCode: string;
for (const [code, country] of Object.entries(countries)) { for (const [code, country] of Object.entries(countries)) {
if (country.name === countryName) { if (
country.name === countryName ||
country.name ===
FinancialModelingPrepService.countriesMapping[countryName]
) {
countryCode = code; countryCode = code;
break; break;
} }
@ -136,11 +164,10 @@ export class FinancialModelingPrepService implements DataProviderInterface {
code: countryCode, code: countryCode,
weight: parseFloat(weightPercentage.slice(0, -1)) / 100 weight: parseFloat(weightPercentage.slice(0, -1)) / 100
}; };
} });
);
const etfHoldings = await fetch( const etfHoldings = await fetch(
`${this.getUrl({ version: 'stable' })}/etf/holdings?symbol=${symbol}&apikey=${this.apiKey}`, `${this.getUrl({ version: 'stable' })}/etf/holdings?${queryParams.toString()}`,
{ {
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }
@ -159,7 +186,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
); );
const [etfInformation] = await fetch( const [etfInformation] = await fetch(
`${this.getUrl({ version: 'stable' })}/etf/info?symbol=${symbol}&apikey=${this.apiKey}`, `${this.getUrl({ version: 'stable' })}/etf/info?${queryParams.toString()}`,
{ {
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }
@ -170,7 +197,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
} }
const etfSectorWeightings = await fetch( const etfSectorWeightings = await fetch(
`${this.getUrl({ version: 'stable' })}/etf/sector-weightings?symbol=${symbol}&apikey=${this.apiKey}`, `${this.getUrl({ version: 'stable' })}/etf/sector-weightings?${queryParams.toString()}`,
{ {
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }
@ -242,12 +269,17 @@ export class FinancialModelingPrepService implements DataProviderInterface {
} }
try { try {
const queryParams = new URLSearchParams({
symbol,
apikey: this.apiKey
});
const response: { const response: {
[date: string]: DataProviderHistoricalResponse; [date: string]: DataProviderHistoricalResponse;
} = {}; } = {};
const dividends = await fetch( const dividends = await fetch(
`${this.getUrl({ version: 'stable' })}/dividends?symbol=${symbol}&apikey=${this.apiKey}`, `${this.getUrl({ version: 'stable' })}/dividends?${queryParams.toString()}`,
{ {
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }
@ -307,8 +339,15 @@ export class FinancialModelingPrepService implements DataProviderInterface {
? addYears(currentFrom, MAX_YEARS_PER_REQUEST) ? addYears(currentFrom, MAX_YEARS_PER_REQUEST)
: to; : to;
const queryParams = new URLSearchParams({
symbol,
apikey: this.apiKey,
from: format(currentFrom, DATE_FORMAT),
to: format(currentTo, DATE_FORMAT)
});
const historical = await fetch( const historical = await fetch(
`${this.getUrl({ version: 'stable' })}/historical-price-eod/full?symbol=${symbol}&apikey=${this.apiKey}&from=${format(currentFrom, DATE_FORMAT)}&to=${format(currentTo, DATE_FORMAT)}`, `${this.getUrl({ version: 'stable' })}/historical-price-eod/full?${queryParams.toString()}`,
{ {
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }
@ -363,6 +402,11 @@ export class FinancialModelingPrepService implements DataProviderInterface {
[symbol: string]: Pick<SymbolProfile, 'currency'>; [symbol: string]: Pick<SymbolProfile, 'currency'>;
} = {}; } = {};
const queryParams = new URLSearchParams({
symbols: symbols.join(','),
apikey: this.apiKey
});
const [assetProfileResolutions, quotes] = await Promise.all([ const [assetProfileResolutions, quotes] = await Promise.all([
this.prismaService.assetProfileResolution.findMany({ this.prismaService.assetProfileResolution.findMany({
where: { where: {
@ -371,7 +415,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
} }
}), }),
fetch( fetch(
`${this.getUrl({ version: 'stable' })}/batch-quote-short?symbols=${symbols.join(',')}&apikey=${this.apiKey}`, `${this.getUrl({ version: 'stable' })}/batch-quote-short?${queryParams.toString()}`,
{ {
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }
@ -463,12 +507,18 @@ export class FinancialModelingPrepService implements DataProviderInterface {
const assetProfileBySymbolMap: { const assetProfileBySymbolMap: {
[symbol: string]: Partial<SymbolProfile>; [symbol: string]: Partial<SymbolProfile>;
} = {}; } = {};
let items: LookupItem[] = []; let items: LookupItem[] = [];
try { try {
if (isISIN(query?.toUpperCase())) { if (isISIN(query?.toUpperCase())) {
const queryParams = new URLSearchParams({
apikey: this.apiKey,
isin: query.toUpperCase()
});
const result = await fetch( const result = await fetch(
`${this.getUrl({ version: 'stable' })}/search-isin?isin=${query.toUpperCase()}&apikey=${this.apiKey}`, `${this.getUrl({ version: 'stable' })}/search-isin?${queryParams.toString()}`,
{ {
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
} }
@ -494,8 +544,13 @@ export class FinancialModelingPrepService implements DataProviderInterface {
}; };
}); });
} else { } else {
const queryParams = new URLSearchParams({
query,
apikey: this.apiKey
});
const result = await fetch( const result = await fetch(
`${this.getUrl({ version: 'stable' })}/search-symbol?query=${query}&apikey=${this.apiKey}`, `${this.getUrl({ version: 'stable' })}/search-symbol?${queryParams.toString()}`,
{ {
signal: AbortSignal.timeout( signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT') this.configurationService.get('REQUEST_TIMEOUT')

34
apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts

@ -116,11 +116,14 @@ export class GhostfolioService implements DataProviderInterface {
} = {}; } = {};
try { try {
const queryParams = new URLSearchParams({
granularity,
from: format(from, DATE_FORMAT),
to: format(to, DATE_FORMAT)
});
const response = await fetch( const response = await fetch(
`${this.URL}/v2/data-providers/ghostfolio/dividends/${symbol}?from=${format(from, DATE_FORMAT)}&granularity=${granularity}&to=${format( `${this.URL}/v2/data-providers/ghostfolio/dividends/${symbol}?${queryParams.toString()}`,
to,
DATE_FORMAT
)}`,
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
@ -165,11 +168,14 @@ export class GhostfolioService implements DataProviderInterface {
[symbol: string]: { [date: string]: DataProviderHistoricalResponse }; [symbol: string]: { [date: string]: DataProviderHistoricalResponse };
}> { }> {
try { try {
const queryParams = new URLSearchParams({
granularity,
from: format(from, DATE_FORMAT),
to: format(to, DATE_FORMAT)
});
const response = await fetch( const response = await fetch(
`${this.URL}/v2/data-providers/ghostfolio/historical/${symbol}?from=${format(from, DATE_FORMAT)}&granularity=${granularity}&to=${format( `${this.URL}/v2/data-providers/ghostfolio/historical/${symbol}?${queryParams.toString()}`,
to,
DATE_FORMAT
)}`,
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
@ -235,8 +241,12 @@ export class GhostfolioService implements DataProviderInterface {
} }
try { try {
const queryParams = new URLSearchParams({
symbols: symbols.join(',')
});
const response = await fetch( const response = await fetch(
`${this.URL}/v2/data-providers/ghostfolio/quotes?symbols=${symbols.join(',')}`, `${this.URL}/v2/data-providers/ghostfolio/quotes?${queryParams.toString()}`,
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)
@ -288,8 +298,12 @@ export class GhostfolioService implements DataProviderInterface {
let searchResult: LookupResponse = { items: [] }; let searchResult: LookupResponse = { items: [] };
try { try {
const queryParams = new URLSearchParams({
query
});
const response = await fetch( const response = await fetch(
`${this.URL}/v2/data-providers/ghostfolio/lookup?query=${query}`, `${this.URL}/v2/data-providers/ghostfolio/lookup?${queryParams.toString()}`,
{ {
headers: await this.getRequestHeaders(), headers: await this.getRequestHeaders(),
signal: AbortSignal.timeout(requestTimeout) signal: AbortSignal.timeout(requestTimeout)

2
apps/client/src/app/components/access-table/access-table.component.html

@ -73,7 +73,7 @@
<button mat-menu-item (click)="onUpdateAccess(element.id)"> <button mat-menu-item (click)="onUpdateAccess(element.id)">
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" /> <ion-icon class="mr-2" name="create-outline" />
<span i18n>Edit</span> <span><ng-container i18n>Edit</ng-container>...</span>
</span> </span>
</button> </button>
} }

6
apps/client/src/app/components/admin-jobs/admin-jobs.html

@ -205,14 +205,16 @@
</button> </button>
<mat-menu #jobActionsMenu="matMenu" xPosition="before"> <mat-menu #jobActionsMenu="matMenu" xPosition="before">
<button mat-menu-item (click)="onViewData(element.data)"> <button mat-menu-item (click)="onViewData(element.data)">
<ng-container i18n>View Data</ng-container> <span><ng-container i18n>View Data</ng-container>...</span>
</button> </button>
<button <button
mat-menu-item mat-menu-item
[disabled]="element.stacktrace?.length <= 0" [disabled]="element.stacktrace?.length <= 0"
(click)="onViewStacktrace(element.stacktrace)" (click)="onViewStacktrace(element.stacktrace)"
> >
<ng-container i18n>View Stacktrace</ng-container> <span
><ng-container i18n>View Stacktrace</ng-container>...</span
>
</button> </button>
<button mat-menu-item (click)="onExecuteJob(element.id)"> <button mat-menu-item (click)="onExecuteJob(element.id)">
<ng-container i18n>Execute Job</ng-container> <ng-container i18n>Execute Job</ng-container>

2
apps/client/src/app/components/admin-market-data/admin-market-data.html

@ -264,7 +264,7 @@
> >
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" /> <ion-icon class="mr-2" name="create-outline" />
<span i18n>Edit</span> <span><ng-container i18n>Edit</ng-container>...</span>
</span> </span>
</a> </a>
<hr class="m-0" /> <hr class="m-0" />

2
apps/client/src/app/components/admin-platform/admin-platform.component.html

@ -71,7 +71,7 @@
<button mat-menu-item (click)="onUpdatePlatform(element)"> <button mat-menu-item (click)="onUpdatePlatform(element)">
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" /> <ion-icon class="mr-2" name="create-outline" />
<span i18n>Edit</span> <span><ng-container i18n>Edit</ng-container>...</span>
</span> </span>
</button> </button>
<hr class="m-0" /> <hr class="m-0" />

2
apps/client/src/app/components/admin-tag/admin-tag.component.html

@ -64,7 +64,7 @@
<button mat-menu-item (click)="onUpdateTag(element)"> <button mat-menu-item (click)="onUpdateTag(element)">
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" /> <ion-icon class="mr-2" name="create-outline" />
<span i18n>Edit</span> <span><ng-container i18n>Edit</ng-container>...</span>
</span> </span>
</button> </button>
<hr class="m-0" /> <hr class="m-0" />

4
apps/client/src/app/components/admin-users/admin-users.html

@ -222,7 +222,9 @@
> >
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="person-outline" /> <ion-icon class="mr-2" name="person-outline" />
<span i18n>View Details</span> <span
><ng-container i18n>View Details</ng-container>...</span
>
</span> </span>
</button> </button>
@if (hasPermissionToImpersonateAllUsers) { @if (hasPermissionToImpersonateAllUsers) {

6
libs/ui/src/lib/accounts-table/accounts-table.component.html

@ -7,7 +7,7 @@
(click)="onTransferBalance()" (click)="onTransferBalance()"
> >
<ion-icon class="mr-2" name="arrow-redo-outline" /> <ion-icon class="mr-2" name="arrow-redo-outline" />
<ng-container i18n>Transfer Cash Balance</ng-container>... <span><ng-container i18n>Transfer Cash Balance</ng-container>...</span>
</button> </button>
</div> </div>
} }
@ -304,13 +304,13 @@
<button mat-menu-item (click)="onOpenAccountDetailDialog(element.id)"> <button mat-menu-item (click)="onOpenAccountDetailDialog(element.id)">
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="wallet-outline" /> <ion-icon class="mr-2" name="wallet-outline" />
<span i18n>View Details</span> <span><ng-container i18n>View Details</ng-container>...</span>
</span> </span>
</button> </button>
<button mat-menu-item (click)="onUpdateAccount(element)"> <button mat-menu-item (click)="onUpdateAccount(element)">
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" /> <ion-icon class="mr-2" name="create-outline" />
<span i18n>Edit</span> <span><ng-container i18n>Edit</ng-container>...</span>
</span> </span>
</button> </button>
<hr class="m-0" /> <hr class="m-0" />

18
libs/ui/src/lib/activities-table/activities-table.component.html

@ -6,7 +6,7 @@
(click)="onImport()" (click)="onImport()"
> >
<ion-icon class="mr-2" name="cloud-upload-outline" /> <ion-icon class="mr-2" name="cloud-upload-outline" />
<ng-container i18n>Import Activities</ng-container>... <span><ng-container i18n>Import Activities</ng-container>...</span>
</button> </button>
@if (hasPermissionToExportActivities) { @if (hasPermissionToExportActivities) {
<button <button
@ -26,7 +26,7 @@
> >
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="color-wand-outline" /> <ion-icon class="mr-2" name="color-wand-outline" />
<ng-container i18n>Import Dividends</ng-container>... <span><ng-container i18n>Import Dividends</ng-container>...</span>
</span> </span>
</button> </button>
@if (hasPermissionToExportActivities) { @if (hasPermissionToExportActivities) {
@ -379,7 +379,9 @@
> >
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="cloud-upload-outline" /> <ion-icon class="mr-2" name="cloud-upload-outline" />
<ng-container i18n>Import Activities</ng-container>... <span
><ng-container i18n>Import Activities</ng-container>...</span
>
</span> </span>
</button> </button>
} }
@ -391,7 +393,9 @@
> >
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="color-wand-outline" /> <ion-icon class="mr-2" name="color-wand-outline" />
<ng-container i18n>Import Dividends</ng-container>... <span
><ng-container i18n>Import Dividends</ng-container>...</span
>
</span> </span>
</button> </button>
} }
@ -443,20 +447,20 @@
<button mat-menu-item (click)="onClickActivity(element)"> <button mat-menu-item (click)="onClickActivity(element)">
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="tablet-landscape-outline" /> <ion-icon class="mr-2" name="tablet-landscape-outline" />
<span i18n>View Holding</span> <span><ng-container i18n>View Holding</ng-container>...</span>
</span> </span>
</button> </button>
} }
<button mat-menu-item (click)="onUpdateActivity(element)"> <button mat-menu-item (click)="onUpdateActivity(element)">
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" /> <ion-icon class="mr-2" name="create-outline" />
<span i18n>Edit</span> <span><ng-container i18n>Edit</ng-container>...</span>
</span> </span>
</button> </button>
<button mat-menu-item (click)="onCloneActivity(element)"> <button mat-menu-item (click)="onCloneActivity(element)">
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="copy-outline" /> <ion-icon class="mr-2" name="copy-outline" />
<span i18n>Clone</span> <span><ng-container i18n>Clone</ng-container>...</span>
</span> </span>
</button> </button>
<button <button

46
libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.stories.ts

@ -22,7 +22,7 @@ export default {
type Story = StoryObj<GfPortfolioProportionChartComponent>; type Story = StoryObj<GfPortfolioProportionChartComponent>;
export const Simple: Story = { export const Default: Story = {
args: { args: {
baseCurrency: 'USD', baseCurrency: 'USD',
data: { data: {
@ -37,3 +37,47 @@ export const Simple: Story = {
locale: 'en-US' locale: 'en-US'
} }
}; };
export const InPercentage: Story = {
args: {
data: {
US: { name: 'United States', value: 0.6515000000000001 },
NL: { name: 'Netherlands', value: 0.006 },
DE: { name: 'Germany', value: 0.0031 },
GB: { name: 'United Kingdom', value: 0.0124 },
CA: { name: 'Canada', value: 0.0247 },
IE: { name: 'Ireland', value: 0.0112 },
SE: { name: 'Sweden', value: 0.0016 },
ES: { name: 'Spain', value: 0.0042 },
AU: { name: 'Australia', value: 0.0022 },
FR: { name: 'France', value: 0.0012 },
UY: { name: 'Uruguay', value: 0.0012 },
CH: { name: 'Switzerland', value: 0.004099999999999999 },
LU: { name: 'Luxembourg', value: 0.0012 },
BR: { name: 'Brazil', value: 0.0006 },
HK: { name: 'Hong Kong', value: 0.0006 },
IT: { name: 'Italy', value: 0.0005 },
CN: { name: 'China', value: 0.002 },
KR: { name: 'South Korea', value: 0.0006 },
BM: { name: 'Bermuda', value: 0.0011 },
ZA: { name: 'South Africa', value: 0.0004 },
SG: { name: 'Singapore', value: 0.0003 },
IL: { name: 'Israel', value: 0.001 },
DK: { name: 'Denmark', value: 0.0002 },
PE: { name: 'Peru', value: 0.0002 },
NO: { name: 'Norway', value: 0.0002 },
KY: { name: 'Cayman Islands', value: 0.0001 },
IN: { name: 'India', value: 0.0001 },
TW: { name: 'Taiwan', value: 0.0002 },
GR: { name: 'Greece', value: 0.0001 },
CL: { name: 'Chile', value: 0.0001 },
MX: { name: 'Mexico', value: 0 },
RU: { name: 'Russia', value: 0 },
IS: { name: 'Iceland', value: 0 },
JP: { name: 'Japan', value: 0 },
BE: { name: 'Belgium', value: 0 }
},
isInPercent: true,
keys: ['name']
}
};

20
package-lock.json

@ -1,12 +1,12 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.219.0", "version": "2.220.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.219.0", "version": "2.220.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
@ -23,7 +23,7 @@
"@angular/service-worker": "20.2.4", "@angular/service-worker": "20.2.4",
"@codewithdan/observable-store": "2.2.15", "@codewithdan/observable-store": "2.2.15",
"@date-fns/utc": "2.1.0", "@date-fns/utc": "2.1.0",
"@internationalized/number": "3.6.3", "@internationalized/number": "3.6.5",
"@ionic/angular": "8.7.8", "@ionic/angular": "8.7.8",
"@keyv/redis": "4.4.0", "@keyv/redis": "4.4.0",
"@nestjs/bull": "11.0.4", "@nestjs/bull": "11.0.4",
@ -140,7 +140,7 @@
"jest-environment-jsdom": "29.7.0", "jest-environment-jsdom": "29.7.0",
"jest-preset-angular": "14.6.0", "jest-preset-angular": "14.6.0",
"nx": "21.5.1", "nx": "21.5.1",
"prettier": "3.6.2", "prettier": "3.7.3",
"prettier-plugin-organize-attributes": "1.0.0", "prettier-plugin-organize-attributes": "1.0.0",
"prisma": "6.19.0", "prisma": "6.19.0",
"react": "18.2.0", "react": "18.2.0",
@ -6031,9 +6031,9 @@
} }
}, },
"node_modules/@internationalized/number": { "node_modules/@internationalized/number": {
"version": "3.6.3", "version": "3.6.5",
"resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.3.tgz", "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.5.tgz",
"integrity": "sha512-p+Zh1sb6EfrfVaS86jlHGQ9HA66fJhV9x5LiE5vCbZtXEHAuhcmUZUdZ4WrFpUBfNalr2OkAJI5AcKEQF+Lebw==", "integrity": "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@swc/helpers": "^0.5.0" "@swc/helpers": "^0.5.0"
@ -35749,9 +35749,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.6.2", "version": "3.7.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.3.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "integrity": "sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {

6
package.json

@ -1,6 +1,6 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.219.0", "version": "2.220.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",
@ -67,7 +67,7 @@
"@angular/service-worker": "20.2.4", "@angular/service-worker": "20.2.4",
"@codewithdan/observable-store": "2.2.15", "@codewithdan/observable-store": "2.2.15",
"@date-fns/utc": "2.1.0", "@date-fns/utc": "2.1.0",
"@internationalized/number": "3.6.3", "@internationalized/number": "3.6.5",
"@ionic/angular": "8.7.8", "@ionic/angular": "8.7.8",
"@keyv/redis": "4.4.0", "@keyv/redis": "4.4.0",
"@nestjs/bull": "11.0.4", "@nestjs/bull": "11.0.4",
@ -184,7 +184,7 @@
"jest-environment-jsdom": "29.7.0", "jest-environment-jsdom": "29.7.0",
"jest-preset-angular": "14.6.0", "jest-preset-angular": "14.6.0",
"nx": "21.5.1", "nx": "21.5.1",
"prettier": "3.6.2", "prettier": "3.7.3",
"prettier-plugin-organize-attributes": "1.0.0", "prettier-plugin-organize-attributes": "1.0.0",
"prisma": "6.19.0", "prisma": "6.19.0",
"react": "18.2.0", "react": "18.2.0",

Loading…
Cancel
Save