From dc5ac1ed00dd80506bec8edfbd5c59dd38973ace Mon Sep 17 00:00:00 2001 From: KenTandrian Date: Thu, 14 May 2026 13:02:48 +0700 Subject: [PATCH 1/4] fix(client): resolve errors --- .../investment-chart.component.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.ts b/apps/client/src/app/components/investment-chart/investment-chart.component.ts index 21a7ac85a..6b269d708 100644 --- a/apps/client/src/app/components/investment-chart/investment-chart.component.ts +++ b/apps/client/src/app/components/investment-chart/investment-chart.component.ts @@ -118,7 +118,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { borderWidth: this.groupBy ? 0 : 1, data: this.investments.map(({ date, investment }) => { return { - x: parseDate(date).getTime(), + x: parseDate(date)?.getTime() ?? null, y: this.isInPercentage ? investment * 100 : investment }; }), @@ -138,7 +138,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { borderWidth: 2, data: this.values.map(({ date, value }) => { return { - x: parseDate(date).getTime(), + x: parseDate(date)?.getTime() ?? null, y: this.isInPercentage ? value * 100 : value }; }), @@ -165,14 +165,14 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { this.getTooltipPluginConfiguration(); const annotations = this.chart.options.plugins.annotation - .annotations as Record>; + ?.annotations as Record>; if (this.savingsRate && annotations.savingsRate) { annotations.savingsRate.value = this.savingsRate; } this.chart.update(); } else { - this.chart = new Chart(this.chartCanvas.nativeElement, { + this.chart = new Chart<'bar' | 'line'>(this.chartCanvas.nativeElement, { data: chartData, options: { animation: false, @@ -305,8 +305,12 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { } private isInFuture(aContext: ScriptableLineSegmentContext, aValue: T) { - return isAfter(new Date(aContext?.p1?.parsed?.x), new Date()) - ? aValue - : undefined; + const xValue = aContext?.p1?.parsed?.x; + + if (xValue == null) { + return undefined; + } + + return isAfter(new Date(xValue), new Date()) ? aValue : undefined; } } From 3a24348d39f98ec4ca38100415bcea078431e81d Mon Sep 17 00:00:00 2001 From: KenTandrian Date: Thu, 14 May 2026 13:06:39 +0700 Subject: [PATCH 2/4] feat(client): enforce encapsulation --- .../investment-chart.component.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.ts b/apps/client/src/app/components/investment-chart/investment-chart.component.ts index 6b269d708..d9fc1930a 100644 --- a/apps/client/src/app/components/investment-chart/investment-chart.component.ts +++ b/apps/client/src/app/components/investment-chart/investment-chart.component.ts @@ -55,20 +55,20 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; templateUrl: './investment-chart.component.html' }) export class GfInvestmentChartComponent implements OnChanges, OnDestroy { - @Input() benchmarkDataItems: InvestmentItem[] = []; - @Input() benchmarkDataLabel = ''; - @Input() colorScheme: ColorScheme; - @Input() currency: string; - @Input() groupBy: GroupBy; - @Input() historicalDataItems: LineChartItem[] = []; - @Input() isInPercentage = false; - @Input() isLoading = false; - @Input() locale = getLocale(); - @Input() savingsRate = 0; + @Input() public benchmarkDataItems: InvestmentItem[] = []; + @Input() public benchmarkDataLabel = ''; + @Input() public colorScheme: ColorScheme; + @Input() public currency: string; + @Input() public groupBy: GroupBy; + @Input() public historicalDataItems: LineChartItem[] = []; + @Input() public isInPercentage = false; + @Input() public isLoading = false; + @Input() public locale = getLocale(); + @Input() public savingsRate = 0; - @ViewChild('chartCanvas') chartCanvas: ElementRef; + @ViewChild('chartCanvas') private chartCanvas: ElementRef; - public chart: Chart<'bar' | 'line'>; + private chart: Chart<'bar' | 'line'>; private investments: InvestmentItem[]; private values: LineChartItem[]; From 31f0b10053c2be3e9cb26f6e86452a69f72caaf3 Mon Sep 17 00:00:00 2001 From: KenTandrian Date: Thu, 14 May 2026 13:12:35 +0700 Subject: [PATCH 3/4] feat(client): use viewChild signals --- .../investment-chart.component.ts | 202 +++++++++--------- 1 file changed, 103 insertions(+), 99 deletions(-) diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.ts b/apps/client/src/app/components/investment-chart/investment-chart.component.ts index d9fc1930a..d42975f9d 100644 --- a/apps/client/src/app/components/investment-chart/investment-chart.component.ts +++ b/apps/client/src/app/components/investment-chart/investment-chart.component.ts @@ -24,7 +24,7 @@ import { Input, OnChanges, OnDestroy, - ViewChild + viewChild } from '@angular/core'; import { BarController, @@ -66,7 +66,8 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { @Input() public locale = getLocale(); @Input() public savingsRate = 0; - @ViewChild('chartCanvas') private chartCanvas: ElementRef; + private readonly chartCanvas = + viewChild.required>('chartCanvas'); private chart: Chart<'bar' | 'line'>; private investments: InvestmentItem[]; @@ -172,116 +173,119 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { this.chart.update(); } else { - this.chart = new Chart<'bar' | 'line'>(this.chartCanvas.nativeElement, { - data: chartData, - options: { - animation: false, - elements: { - line: { - tension: 0 + this.chart = new Chart<'bar' | 'line'>( + this.chartCanvas().nativeElement, + { + data: chartData, + options: { + animation: false, + elements: { + line: { + tension: 0 + }, + point: { + hoverBackgroundColor: getBackgroundColor(this.colorScheme), + hoverRadius: 2, + radius: 0 + } }, - point: { - hoverBackgroundColor: getBackgroundColor(this.colorScheme), - hoverRadius: 2, - radius: 0 - } - }, - interaction: { intersect: false, mode: 'index' }, - maintainAspectRatio: true, - plugins: { - annotation: { - annotations: { - savingsRate: this.savingsRate - ? { - borderColor: `rgba(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b}, 0.75)`, - borderWidth: 1, - label: { - backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`, - borderRadius: 2, - color: 'white', - content: $localize`Savings Rate`, - display: true, - font: { size: 10, weight: 'normal' }, - padding: { - x: 4, - y: 2 + interaction: { intersect: false, mode: 'index' }, + maintainAspectRatio: true, + plugins: { + annotation: { + annotations: { + savingsRate: this.savingsRate + ? { + borderColor: `rgba(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b}, 0.75)`, + borderWidth: 1, + label: { + backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`, + borderRadius: 2, + color: 'white', + content: $localize`Savings Rate`, + display: true, + font: { size: 10, weight: 'normal' }, + padding: { + x: 4, + y: 2 + }, + position: 'start' }, - position: 'start' - }, - scaleID: 'y', - type: 'line', - value: this.savingsRate - } - : undefined, - yAxis: { - borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`, - borderWidth: 1, - scaleID: 'y', - type: 'line', - value: 0 + scaleID: 'y', + type: 'line', + value: this.savingsRate + } + : undefined, + yAxis: { + borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`, + borderWidth: 1, + scaleID: 'y', + type: 'line', + value: 0 + } } - } - }, - legend: { - display: false - }, - tooltip: this.getTooltipPluginConfiguration(), - verticalHoverLine: { - color: `rgba(${getTextColor(this.colorScheme)}, 0.1)` - } - }, - responsive: true, - scales: { - x: { - border: { - color: `rgba(${getTextColor(this.colorScheme)}, 0.1)`, - width: this.groupBy ? 0 : 1 }, - display: true, - grid: { + legend: { display: false }, - type: 'time', - time: { - tooltipFormat: getDateFormatString(this.locale), - unit: 'year' + tooltip: this.getTooltipPluginConfiguration(), + verticalHoverLine: { + color: `rgba(${getTextColor(this.colorScheme)}, 0.1)` } }, - y: { - border: { - display: false - }, - display: !this.isInPercentage, - grid: { - color: ({ scale, tick }) => { - if ( - tick.value === 0 || - tick.value === scale.max || - tick.value === scale.min - ) { - return `rgba(${getTextColor(this.colorScheme)}, 0.1)`; - } - - return 'transparent'; + responsive: true, + scales: { + x: { + border: { + color: `rgba(${getTextColor(this.colorScheme)}, 0.1)`, + width: this.groupBy ? 0 : 1 + }, + display: true, + grid: { + display: false + }, + type: 'time', + time: { + tooltipFormat: getDateFormatString(this.locale), + unit: 'year' } }, - position: 'right', - ticks: { - callback: (value: number) => { - return transformTickToAbbreviation(value); + y: { + border: { + display: false }, - display: true, - mirror: true, - z: 1 + display: !this.isInPercentage, + grid: { + color: ({ scale, tick }) => { + if ( + tick.value === 0 || + tick.value === scale.max || + tick.value === scale.min + ) { + return `rgba(${getTextColor(this.colorScheme)}, 0.1)`; + } + + return 'transparent'; + } + }, + position: 'right', + ticks: { + callback: (value: number) => { + return transformTickToAbbreviation(value); + }, + display: true, + mirror: true, + z: 1 + } } } - } - }, - plugins: [ - getVerticalHoverLinePlugin(this.chartCanvas, this.colorScheme) - ], - type: this.groupBy ? 'bar' : 'line' - }); + }, + plugins: [ + getVerticalHoverLinePlugin(this.chartCanvas(), this.colorScheme) + ], + type: this.groupBy ? 'bar' : 'line' + } + ); } } } From 6153e80c5a325e87c5808c3a98ed81811b9a30bc Mon Sep 17 00:00:00 2001 From: KenTandrian Date: Thu, 14 May 2026 13:17:50 +0700 Subject: [PATCH 4/4] feat(client): enforce immutability --- .../investment-chart.component.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.ts b/apps/client/src/app/components/investment-chart/investment-chart.component.ts index d42975f9d..691133009 100644 --- a/apps/client/src/app/components/investment-chart/investment-chart.component.ts +++ b/apps/client/src/app/components/investment-chart/investment-chart.component.ts @@ -55,16 +55,16 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; templateUrl: './investment-chart.component.html' }) export class GfInvestmentChartComponent implements OnChanges, OnDestroy { - @Input() public benchmarkDataItems: InvestmentItem[] = []; - @Input() public benchmarkDataLabel = ''; - @Input() public colorScheme: ColorScheme; - @Input() public currency: string; - @Input() public groupBy: GroupBy; - @Input() public historicalDataItems: LineChartItem[] = []; - @Input() public isInPercentage = false; - @Input() public isLoading = false; - @Input() public locale = getLocale(); - @Input() public savingsRate = 0; + @Input() public readonly benchmarkDataItems: InvestmentItem[] = []; + @Input() public readonly benchmarkDataLabel = ''; + @Input() public readonly colorScheme: ColorScheme; + @Input() public readonly currency: string; + @Input() public readonly groupBy: GroupBy; + @Input() public readonly historicalDataItems: LineChartItem[] = []; + @Input() public readonly isInPercentage = false; + @Input() public readonly isLoading = false; + @Input() public readonly locale = getLocale(); + @Input() public readonly savingsRate = 0; private readonly chartCanvas = viewChild.required>('chartCanvas');