Browse Source

Task/improve label of cash positions (#8007)

* Improve label of cash positions

* Update changelog
pull/7550/merge
Thomas Kaul 5 days ago
committed by GitHub
parent
commit
acd14c6298
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 3
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  3. 3
      apps/client/src/app/pages/public/public-page.component.ts
  4. 47
      libs/common/src/lib/helper.spec.ts
  5. 16
      libs/common/src/lib/helper.ts
  6. 13
      libs/ui/src/lib/holdings-table/holdings-table.component.html
  7. 12
      libs/ui/src/lib/holdings-table/holdings-table.component.ts
  8. 14
      libs/ui/src/lib/treemap-chart/treemap-chart.component.ts

1
CHANGELOG.md

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Improved the label of the cash positions in the holdings charts and table
- Upgraded `@openrouter/ai-sdk-provider` from version `3.0.0` to `3.1.0` - Upgraded `@openrouter/ai-sdk-provider` from version `3.0.0` to `3.1.0`
- Upgraded `ai` from version `7.0.37` to `7.0.114` - Upgraded `ai` from version `7.0.37` to `7.0.114`
- Upgraded `dotenv` from version `17.4.2` to `18.0.3` - Upgraded `dotenv` from version `17.4.2` to `18.0.3`

3
apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts

@ -11,6 +11,7 @@ import {
convertValuesToPercentagesOfTotal, convertValuesToPercentagesOfTotal,
getAssetProfileIdentifier, getAssetProfileIdentifier,
getCountryName, getCountryName,
getHoldingName,
isCashPosition isCashPosition
} from '@ghostfolio/common/helper'; } from '@ghostfolio/common/helper';
import { import {
@ -533,7 +534,7 @@ export class GfAllocationsPageComponent implements OnInit {
value, value,
dataSource: position.assetProfile.dataSource, dataSource: position.assetProfile.dataSource,
isClickable: canOpenHoldingDetail(position), isClickable: canOpenHoldingDetail(position),
name: position.assetProfile.name ?? '' name: getHoldingName(position.assetProfile)
}; };
} }
} }

3
apps/client/src/app/pages/public/public-page.component.ts

@ -3,6 +3,7 @@ import {
convertValuesToPercentagesOfTotal, convertValuesToPercentagesOfTotal,
getAssetProfileIdentifier, getAssetProfileIdentifier,
getCountryName, getCountryName,
getHoldingName,
isCashPosition isCashPosition
} from '@ghostfolio/common/helper'; } from '@ghostfolio/common/helper';
import { import {
@ -260,7 +261,7 @@ export class GfPublicPageComponent implements OnInit {
this.symbols[symbol] = { this.symbols[symbol] = {
symbol, symbol,
value, value,
name: position.assetProfile.name ?? symbol name: getHoldingName(position.assetProfile)
}; };
} }
} }

47
libs/common/src/lib/helper.spec.ts

@ -7,6 +7,7 @@ import {
extractNumberFromString, extractNumberFromString,
getCountryCodeFromCurrency, getCountryCodeFromCurrency,
getEmojiFlag, getEmojiFlag,
getHoldingName,
getNumberFormatGroup, getNumberFormatGroup,
getStringOrNull, getStringOrNull,
getStringOrUndefined, getStringOrUndefined,
@ -143,6 +144,52 @@ describe('Helper', () => {
}); });
}); });
describe('Get holding name', () => {
it('Cash position', () => {
expect(
getHoldingName({
assetSubClass: 'CASH',
assetSubClassLabel: 'Cash',
name: 'CHF',
symbol: 'CHF'
})
).toEqual('Cash (CHF)');
});
it('Cash position without asset sub class label', () => {
expect(
getHoldingName({
assetSubClass: 'CASH',
assetSubClassLabel: undefined,
name: 'CHF',
symbol: 'CHF'
})
).toEqual('CHF');
});
it('Holding with name', () => {
expect(
getHoldingName({
assetSubClass: 'STOCK',
assetSubClassLabel: 'Stock',
name: 'Apple Inc.',
symbol: 'AAPL'
})
).toEqual('Apple Inc.');
});
it('Holding without name', () => {
expect(
getHoldingName({
assetSubClass: 'STOCK',
assetSubClassLabel: 'Stock',
name: undefined,
symbol: 'AAPL'
})
).toEqual('AAPL');
});
});
describe('Get number format group', () => { describe('Get number format group', () => {
let languageGetter: jest.SpyInstance<string, [], any>; let languageGetter: jest.SpyInstance<string, [], any>;

16
libs/common/src/lib/helper.ts

@ -453,6 +453,22 @@ export function getEmojiFlag(aCountryCode: string) {
}); });
} }
export function getHoldingName({
assetSubClass,
assetSubClassLabel,
name,
symbol
}: Pick<
PortfolioPosition['assetProfile'],
'assetSubClass' | 'assetSubClassLabel' | 'name' | 'symbol'
>) {
if (isCashPosition({ assetSubClass }) && assetSubClassLabel) {
return `${assetSubClassLabel} (${symbol})`;
}
return name ?? symbol;
}
export function getLocale() { export function getLocale() {
return navigator.language ?? DEFAULT_LOCALE; return navigator.language ?? DEFAULT_LOCALE;
} }

13
libs/ui/src/lib/holdings-table/holdings-table.component.html

@ -20,7 +20,7 @@
[countryCode]="getCountryCodeForCashPosition(element)" [countryCode]="getCountryCodeForCashPosition(element)"
[dataSource]="element.assetProfile.dataSource" [dataSource]="element.assetProfile.dataSource"
[symbol]="element.assetProfile.symbol" [symbol]="element.assetProfile.symbol"
[tooltip]="element.assetProfile.name" [tooltip]="getHoldingName(element.assetProfile)"
/> />
</td> </td>
</ng-container> </ng-container>
@ -40,9 +40,14 @@
mat-cell mat-cell
[class.w-100]="mode() === 'simple'" [class.w-100]="mode() === 'simple'"
> >
<div class="text-truncate" [title]="element.assetProfile.name"> @let holdingName = getHoldingName(element.assetProfile);
{{ element.assetProfile.name }}
@if (element.assetProfile.name === element.assetProfile.symbol) { <div class="text-truncate" [title]="holdingName">
{{ holdingName }}
@if (
!isCashPosition(element.assetProfile) &&
element.assetProfile.name === element.assetProfile.symbol
) {
<span>({{ element.assetProfile.assetSubClassLabel }})</span> <span>({{ element.assetProfile.assetSubClassLabel }})</span>
} }
</div> </div>

12
libs/ui/src/lib/holdings-table/holdings-table.component.ts

@ -1,6 +1,7 @@
import { import {
canOpenHoldingDetail, canOpenHoldingDetail,
getCountryCodeFromCurrency, getCountryCodeFromCurrency,
getHoldingName,
getLocale, getLocale,
getLowercase, getLowercase,
isCashPosition isCashPosition
@ -89,6 +90,9 @@ export class GfHoldingsTableComponent {
return columns; return columns;
}); });
protected readonly getHoldingName = getHoldingName;
protected readonly isCashPosition = isCashPosition;
protected readonly isLoading = computed(() => { protected readonly isLoading = computed(() => {
return !this.holdings(); return !this.holdings();
}); });
@ -104,7 +108,13 @@ export class GfHoldingsTableComponent {
}); });
public constructor() { public constructor() {
this.dataSource.sortingDataAccessor = getLowercase; this.dataSource.sortingDataAccessor = (holding, path) => {
if (path === 'assetProfile.name') {
return getHoldingName(holding.assetProfile).toLocaleLowerCase();
}
return getLowercase(holding, path) as number | string;
};
// Reactive data update // Reactive data update
effect(() => { effect(() => {

14
libs/ui/src/lib/treemap-chart/treemap-chart.component.ts

@ -3,7 +3,11 @@ import {
getIntervalFromDateRange getIntervalFromDateRange
} from '@ghostfolio/common/calculation-helper'; } from '@ghostfolio/common/calculation-helper';
import { getTooltipOptions } from '@ghostfolio/common/chart-helper'; import { getTooltipOptions } from '@ghostfolio/common/chart-helper';
import { canOpenHoldingDetail, getLocale } from '@ghostfolio/common/helper'; import {
canOpenHoldingDetail,
getHoldingName,
getLocale
} from '@ghostfolio/common/helper';
import { import {
AssetProfileIdentifier, AssetProfileIdentifier,
PortfolioPosition PortfolioPosition
@ -399,20 +403,18 @@ export class GfTreemapChartComponent
const raw = context.raw as GfTreemapDataPoint; const raw = context.raw as GfTreemapDataPoint;
const allocationInPercentage = `${(raw._data.allocationInPercentage * 100).toFixed(2)}%`; const allocationInPercentage = `${(raw._data.allocationInPercentage * 100).toFixed(2)}%`;
const name = raw._data.assetProfile.name; const name = getHoldingName(raw._data.assetProfile);
const sign = const sign =
raw._data.netPerformancePercentWithCurrencyEffect > 0 ? '+' : ''; raw._data.netPerformancePercentWithCurrencyEffect > 0 ? '+' : '';
const symbol = raw._data.assetProfile.symbol;
const netPerformanceInPercentageWithSign = `${sign}${(raw._data.netPerformancePercentWithCurrencyEffect * 100).toFixed(2)}%`; const netPerformanceInPercentageWithSign = `${sign}${(raw._data.netPerformancePercentWithCurrencyEffect * 100).toFixed(2)}%`;
if (raw._data.valueInBaseCurrency !== null) { if (raw._data.valueInBaseCurrency !== null) {
const value = raw._data.valueInBaseCurrency; const value = raw._data.valueInBaseCurrency;
return [ return [
`${name ?? symbol} (${allocationInPercentage})`, `${name} (${allocationInPercentage})`,
`${value?.toLocaleString(this.locale(), { `${value?.toLocaleString(this.locale(), {
maximumFractionDigits: 2, maximumFractionDigits: 2,
minimumFractionDigits: 2 minimumFractionDigits: 2
@ -429,7 +431,7 @@ export class GfTreemapChartComponent
]; ];
} else { } else {
return [ return [
`${name ?? symbol} (${allocationInPercentage})`, `${name} (${allocationInPercentage})`,
'', '',
$localize`Performance`, $localize`Performance`,
netPerformanceInPercentageWithSign netPerformanceInPercentageWithSign

Loading…
Cancel
Save