Browse Source

Merge remote-tracking branch 'origin/main' into task/enable-eslint-prefer-nullish-coalescing

pull/7317/head
KenTandrian 1 day ago
parent
commit
ceb631496d
  1. 2
      CHANGELOG.md
  2. 4
      apps/client/src/app/components/home-holdings/home-holdings.component.ts
  3. 74
      libs/ui/src/lib/treemap-chart/treemap-chart.component.ts
  4. 4
      package-lock.json
  5. 2
      package.json

2
CHANGELOG.md

@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased ## 3.26.0 - 2026-07-14
### Added ### Added

4
apps/client/src/app/components/home-holdings/home-holdings.component.ts

@ -58,7 +58,7 @@ export class GfHomeHoldingsComponent implements OnInit {
protected hasImpersonationId: boolean; protected hasImpersonationId: boolean;
protected hasPermissionToAccessHoldingsChart: boolean; protected hasPermissionToAccessHoldingsChart: boolean;
protected hasPermissionToCreateActivity: boolean; protected hasPermissionToCreateActivity: boolean;
protected holdings: PortfolioPosition[]; protected holdings: PortfolioPosition[] | undefined;
protected holdingType: HoldingType = 'ACTIVE'; protected holdingType: HoldingType = 'ACTIVE';
protected readonly holdingTypeOptions: ToggleOption[] = [ protected readonly holdingTypeOptions: ToggleOption[] = [
{ label: $localize`Active`, value: 'ACTIVE' }, { label: $localize`Active`, value: 'ACTIVE' },
@ -192,7 +192,7 @@ export class GfHomeHoldingsComponent implements OnInit {
); );
} }
this.holdings = []; this.holdings = undefined;
this.fetchHoldings() this.fetchHoldings()
.pipe(takeUntilDestroyed(this.destroyRef)) .pipe(takeUntilDestroyed(this.destroyRef))

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

@ -15,18 +15,16 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
ElementRef, ElementRef,
EventEmitter, input,
Input,
OnChanges, OnChanges,
OnDestroy, OnDestroy,
Output, output,
ViewChild viewChild
} from '@angular/core'; } from '@angular/core';
import { DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
import { Big } from 'big.js'; import { Big } from 'big.js';
import type { ChartData, TooltipOptions } from 'chart.js'; import type { ChartData, TooltipOptions } from 'chart.js';
import { LinearScale } from 'chart.js'; import { Chart, LinearScale, Tooltip } from 'chart.js';
import { Chart, Tooltip } from 'chart.js';
import { TreemapController, TreemapElement } from 'chartjs-chart-treemap'; import { TreemapController, TreemapElement } from 'chartjs-chart-treemap';
import { isUUID } from 'class-validator'; import { isUUID } from 'class-validator';
import { differenceInDays, max } from 'date-fns'; import { differenceInDays, max } from 'date-fns';
@ -52,33 +50,31 @@ const { gray, green, red } = OpenColor;
export class GfTreemapChartComponent export class GfTreemapChartComponent
implements AfterViewInit, OnChanges, OnDestroy implements AfterViewInit, OnChanges, OnDestroy
{ {
@Input() baseCurrency: string; public readonly baseCurrency = input.required<string>();
@Input() colorScheme: ColorScheme; public readonly colorScheme = input.required<ColorScheme>();
@Input() cursor: string; public readonly cursor = input.required<string>();
@Input() dateRange: DateRange; public readonly dateRange = input.required<DateRange>();
@Input() holdings: PortfolioPosition[]; public readonly holdings = input<PortfolioPosition[]>();
@Input() locale = getLocale(); public readonly locale = input<string>(getLocale());
@Output() treemapChartClicked = new EventEmitter<AssetProfileIdentifier>(); public readonly treemapChartClicked = output<AssetProfileIdentifier>();
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>; protected isLoading = true;
public chart: Chart<'treemap'>; private chart: Chart<'treemap'>;
public isLoading = true; private readonly chartCanvas =
viewChild.required<ElementRef<HTMLCanvasElement>>('chartCanvas');
public constructor() { public constructor() {
Chart.register(LinearScale, Tooltip, TreemapController, TreemapElement); Chart.register(LinearScale, Tooltip, TreemapController, TreemapElement);
} }
public ngAfterViewInit() { public ngAfterViewInit() {
if (this.holdings) { this.initialize();
this.initialize();
}
} }
public ngOnChanges() { public ngOnChanges() {
if (this.holdings) { this.initialize();
this.initialize();
}
} }
public ngOnDestroy() { public ngOnDestroy() {
@ -159,13 +155,19 @@ export class GfTreemapChartComponent
} }
private initialize() { private initialize() {
const holdings = this.holdings();
if (!holdings) {
return;
}
this.isLoading = true; this.isLoading = true;
const { endDate, startDate } = getIntervalFromDateRange({ const { endDate, startDate } = getIntervalFromDateRange({
dateRange: this.dateRange dateRange: this.dateRange()
}); });
const netPerformancePercentsWithCurrencyEffect = this.holdings.map( const netPerformancePercentsWithCurrencyEffect = holdings.map(
({ dateOfFirstActivity, netPerformancePercentWithCurrencyEffect }) => { ({ dateOfFirstActivity, netPerformancePercentWithCurrencyEffect }) => {
return getAnnualizedPerformancePercent({ return getAnnualizedPerformancePercent({
daysInMarket: differenceInDays( daysInMarket: differenceInDays(
@ -301,12 +303,12 @@ export class GfTreemapChartComponent
}, },
spacing: 1, spacing: 1,
// @ts-expect-error: should be PortfolioPosition[] // @ts-expect-error: should be PortfolioPosition[]
tree: this.holdings tree: this.holdings()
} }
] ]
}; };
if (this.chartCanvas) { if (this.chartCanvas()) {
if (this.chart) { if (this.chart) {
this.chart.data = data; this.chart.data = data;
this.chart.options.plugins ??= {}; this.chart.options.plugins ??= {};
@ -315,7 +317,7 @@ export class GfTreemapChartComponent
this.chart.update(); this.chart.update();
} else { } else {
this.chart = new Chart<'treemap'>(this.chartCanvas.nativeElement, { this.chart = new Chart<'treemap'>(this.chartCanvas().nativeElement, {
data, data,
options: { options: {
animation: false, animation: false,
@ -339,9 +341,9 @@ export class GfTreemapChartComponent
} catch {} } catch {}
}, },
onHover: (event, chartElement) => { onHover: (event, chartElement) => {
if (this.cursor) { if (this.cursor()) {
(event.native?.target as HTMLElement).style.cursor = (event.native?.target as HTMLElement).style.cursor =
chartElement[0] ? this.cursor : 'default'; chartElement[0] ? this.cursor() : 'default';
} }
}, },
plugins: { plugins: {
@ -359,9 +361,9 @@ export class GfTreemapChartComponent
private getTooltipPluginConfiguration(): Partial<TooltipOptions<'treemap'>> { private getTooltipPluginConfiguration(): Partial<TooltipOptions<'treemap'>> {
return { return {
...getTooltipOptions({ ...getTooltipOptions({
colorScheme: this.colorScheme, colorScheme: this.colorScheme(),
currency: this.baseCurrency, currency: this.baseCurrency(),
locale: this.locale locale: this.locale()
}), }),
// @ts-expect-error: no need to set all attributes in callbacks // @ts-expect-error: no need to set all attributes in callbacks
callbacks: { callbacks: {
@ -381,19 +383,19 @@ export class GfTreemapChartComponent
return [ return [
`${name ?? symbol} (${allocationInPercentage})`, `${name ?? symbol} (${allocationInPercentage})`,
`${value?.toLocaleString(this.locale, { `${value?.toLocaleString(this.locale(), {
maximumFractionDigits: 2, maximumFractionDigits: 2,
minimumFractionDigits: 2 minimumFractionDigits: 2
})} ${this.baseCurrency}`, })} ${this.baseCurrency()}`,
'', '',
$localize`Change` + ' (' + $localize`Performance` + ')', $localize`Change` + ' (' + $localize`Performance` + ')',
`${sign}${raw._data.netPerformanceWithCurrencyEffect.toLocaleString( `${sign}${raw._data.netPerformanceWithCurrencyEffect.toLocaleString(
this.locale, this.locale(),
{ {
maximumFractionDigits: 2, maximumFractionDigits: 2,
minimumFractionDigits: 2 minimumFractionDigits: 2
} }
)} ${this.baseCurrency} (${netPerformanceInPercentageWithSign})` )} ${this.baseCurrency()} (${netPerformanceInPercentageWithSign})`
]; ];
} else { } else {
return [ return [

4
package-lock.json

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

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "3.25.0", "version": "3.26.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",

Loading…
Cancel
Save