|
|
@ -38,8 +38,11 @@ import { |
|
|
BarElement, |
|
|
BarElement, |
|
|
CategoryScale, |
|
|
CategoryScale, |
|
|
Chart, |
|
|
Chart, |
|
|
|
|
|
type ChartData, |
|
|
|
|
|
type ChartDataset, |
|
|
LinearScale, |
|
|
LinearScale, |
|
|
Tooltip |
|
|
Tooltip, |
|
|
|
|
|
type TooltipOptions |
|
|
} from 'chart.js'; |
|
|
} from 'chart.js'; |
|
|
import 'chartjs-adapter-date-fns'; |
|
|
import 'chartjs-adapter-date-fns'; |
|
|
import Color from 'color'; |
|
|
import Color from 'color'; |
|
|
@ -98,10 +101,15 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>; |
|
|
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>; |
|
|
|
|
|
|
|
|
public calculatorForm = this.formBuilder.group({ |
|
|
public calculatorForm = this.formBuilder.group({ |
|
|
|
|
|
// @ts-ignore
|
|
|
annualInterestRate: new FormControl<number>(undefined), |
|
|
annualInterestRate: new FormControl<number>(undefined), |
|
|
|
|
|
// @ts-ignore
|
|
|
paymentPerPeriod: new FormControl<number>(undefined), |
|
|
paymentPerPeriod: new FormControl<number>(undefined), |
|
|
|
|
|
// @ts-ignore
|
|
|
principalInvestmentAmount: new FormControl<number>(undefined), |
|
|
principalInvestmentAmount: new FormControl<number>(undefined), |
|
|
|
|
|
// @ts-ignore
|
|
|
projectedTotalAmount: new FormControl<number>(undefined), |
|
|
projectedTotalAmount: new FormControl<number>(undefined), |
|
|
|
|
|
// @ts-ignore
|
|
|
retirementDate: new FormControl<Date>(undefined) |
|
|
retirementDate: new FormControl<Date>(undefined) |
|
|
}); |
|
|
}); |
|
|
public chart: Chart<'bar'>; |
|
|
public chart: Chart<'bar'>; |
|
|
@ -148,25 +156,25 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
|
|
|
|
|
|
this.calculatorForm |
|
|
this.calculatorForm |
|
|
.get('annualInterestRate') |
|
|
.get('annualInterestRate') |
|
|
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) |
|
|
?.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) |
|
|
.subscribe((annualInterestRate) => { |
|
|
.subscribe((annualInterestRate) => { |
|
|
this.annualInterestRateChanged.emit(annualInterestRate); |
|
|
this.annualInterestRateChanged.emit(annualInterestRate); |
|
|
}); |
|
|
}); |
|
|
this.calculatorForm |
|
|
this.calculatorForm |
|
|
.get('paymentPerPeriod') |
|
|
.get('paymentPerPeriod') |
|
|
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) |
|
|
?.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) |
|
|
.subscribe((savingsRate) => { |
|
|
.subscribe((savingsRate) => { |
|
|
this.savingsRateChanged.emit(savingsRate); |
|
|
this.savingsRateChanged.emit(savingsRate); |
|
|
}); |
|
|
}); |
|
|
this.calculatorForm |
|
|
this.calculatorForm |
|
|
.get('projectedTotalAmount') |
|
|
.get('projectedTotalAmount') |
|
|
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) |
|
|
?.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) |
|
|
.subscribe((projectedTotalAmount) => { |
|
|
.subscribe((projectedTotalAmount) => { |
|
|
this.projectedTotalAmountChanged.emit(projectedTotalAmount); |
|
|
this.projectedTotalAmountChanged.emit(projectedTotalAmount); |
|
|
}); |
|
|
}); |
|
|
this.calculatorForm |
|
|
this.calculatorForm |
|
|
.get('retirementDate') |
|
|
.get('retirementDate') |
|
|
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) |
|
|
?.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) |
|
|
.subscribe((retirementDate) => { |
|
|
.subscribe((retirementDate) => { |
|
|
this.retirementDateChanged.emit(retirementDate); |
|
|
this.retirementDateChanged.emit(retirementDate); |
|
|
}); |
|
|
}); |
|
|
@ -194,11 +202,11 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
this.calculatorForm.patchValue( |
|
|
this.calculatorForm.patchValue( |
|
|
{ |
|
|
{ |
|
|
annualInterestRate: |
|
|
annualInterestRate: |
|
|
this.calculatorForm.get('annualInterestRate').value, |
|
|
this.calculatorForm.get('annualInterestRate')?.value, |
|
|
paymentPerPeriod: this.getPMT(), |
|
|
paymentPerPeriod: this.getPMT(), |
|
|
principalInvestmentAmount: this.calculatorForm.get( |
|
|
principalInvestmentAmount: this.calculatorForm.get( |
|
|
'principalInvestmentAmount' |
|
|
'principalInvestmentAmount' |
|
|
).value, |
|
|
)?.value, |
|
|
projectedTotalAmount: |
|
|
projectedTotalAmount: |
|
|
Math.round(this.getProjectedTotalAmount()) || 0, |
|
|
Math.round(this.getProjectedTotalAmount()) || 0, |
|
|
retirementDate: |
|
|
retirementDate: |
|
|
@ -208,7 +216,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
emitEvent: false |
|
|
emitEvent: false |
|
|
} |
|
|
} |
|
|
); |
|
|
); |
|
|
this.calculatorForm.get('principalInvestmentAmount').disable(); |
|
|
this.calculatorForm.get('principalInvestmentAmount')?.disable(); |
|
|
|
|
|
|
|
|
this.changeDetectorRef.markForCheck(); |
|
|
this.changeDetectorRef.markForCheck(); |
|
|
}); |
|
|
}); |
|
|
@ -217,34 +225,36 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
if (this.hasPermissionToUpdateUserSettings === true) { |
|
|
if (this.hasPermissionToUpdateUserSettings === true) { |
|
|
this.calculatorForm |
|
|
this.calculatorForm |
|
|
.get('annualInterestRate') |
|
|
.get('annualInterestRate') |
|
|
.enable({ emitEvent: false }); |
|
|
?.enable({ emitEvent: false }); |
|
|
this.calculatorForm.get('paymentPerPeriod').enable({ emitEvent: false }); |
|
|
this.calculatorForm.get('paymentPerPeriod')?.enable({ emitEvent: false }); |
|
|
this.calculatorForm |
|
|
this.calculatorForm |
|
|
.get('projectedTotalAmount') |
|
|
.get('projectedTotalAmount') |
|
|
.enable({ emitEvent: false }); |
|
|
?.enable({ emitEvent: false }); |
|
|
} else { |
|
|
} else { |
|
|
this.calculatorForm |
|
|
this.calculatorForm |
|
|
.get('annualInterestRate') |
|
|
.get('annualInterestRate') |
|
|
.disable({ emitEvent: false }); |
|
|
?.disable({ emitEvent: false }); |
|
|
this.calculatorForm.get('paymentPerPeriod').disable({ emitEvent: false }); |
|
|
this.calculatorForm |
|
|
|
|
|
.get('paymentPerPeriod') |
|
|
|
|
|
?.disable({ emitEvent: false }); |
|
|
this.calculatorForm |
|
|
this.calculatorForm |
|
|
.get('projectedTotalAmount') |
|
|
.get('projectedTotalAmount') |
|
|
.disable({ emitEvent: false }); |
|
|
?.disable({ emitEvent: false }); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.calculatorForm.get('retirementDate').disable({ emitEvent: false }); |
|
|
this.calculatorForm.get('retirementDate')?.disable({ emitEvent: false }); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public setMonthAndYear( |
|
|
public setMonthAndYear( |
|
|
normalizedMonthAndYear: Date, |
|
|
normalizedMonthAndYear: Date, |
|
|
datepicker: MatDatepicker<Date> |
|
|
datepicker: MatDatepicker<Date> |
|
|
) { |
|
|
) { |
|
|
const retirementDate = this.calculatorForm.get('retirementDate').value; |
|
|
const retirementDate = this.calculatorForm.get('retirementDate')!.value; |
|
|
const newRetirementDate = setMonth( |
|
|
const newRetirementDate = setMonth( |
|
|
setYear(retirementDate, normalizedMonthAndYear.getFullYear()), |
|
|
setYear(retirementDate, normalizedMonthAndYear.getFullYear()), |
|
|
normalizedMonthAndYear.getMonth() |
|
|
normalizedMonthAndYear.getMonth() |
|
|
); |
|
|
); |
|
|
this.calculatorForm.get('retirementDate').setValue(newRetirementDate); |
|
|
this.calculatorForm.get('retirementDate')?.setValue(newRetirementDate); |
|
|
datepicker.close(); |
|
|
datepicker.close(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -270,7 +280,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
|
|
|
|
|
|
this.chart.update(); |
|
|
this.chart.update(); |
|
|
} else { |
|
|
} else { |
|
|
this.chart = new Chart(this.chartCanvas.nativeElement, { |
|
|
this.chart = new Chart<'bar'>(this.chartCanvas.nativeElement, { |
|
|
data: chartData, |
|
|
data: chartData, |
|
|
options: { |
|
|
options: { |
|
|
plugins: { |
|
|
plugins: { |
|
|
@ -280,6 +290,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
callbacks: { |
|
|
callbacks: { |
|
|
footer: (items) => { |
|
|
footer: (items) => { |
|
|
const totalAmount = items.reduce( |
|
|
const totalAmount = items.reduce( |
|
|
|
|
|
// @ts-ignore
|
|
|
(a, b) => a + b.parsed.y, |
|
|
(a, b) => a + b.parsed.y, |
|
|
0 |
|
|
0 |
|
|
); |
|
|
); |
|
|
@ -312,7 +323,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
return label; |
|
|
return label; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} as TooltipOptions<'bar'> |
|
|
}, |
|
|
}, |
|
|
responsive: true, |
|
|
responsive: true, |
|
|
scales: { |
|
|
scales: { |
|
|
@ -345,9 +356,9 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
this.isLoading = false; |
|
|
this.isLoading = false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private getChartData() { |
|
|
private getChartData(): ChartData<'bar'> { |
|
|
const currentYear = new Date().getFullYear(); |
|
|
const currentYear = new Date().getFullYear(); |
|
|
const labels = []; |
|
|
const labels: number[] = []; |
|
|
|
|
|
|
|
|
// Principal investment amount
|
|
|
// Principal investment amount
|
|
|
const P: number = this.getP(); |
|
|
const P: number = this.getP(); |
|
|
@ -360,6 +371,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
|
|
|
|
|
|
// Calculate retirement date
|
|
|
// Calculate retirement date
|
|
|
// if we want to retire at month x, we need the projectedTotalAmount at month x-1
|
|
|
// if we want to retire at month x, we need the projectedTotalAmount at month x-1
|
|
|
|
|
|
// @ts-ignore
|
|
|
const lastPeriodDate = sub(this.getRetirementDate(), { months: 1 }); |
|
|
const lastPeriodDate = sub(this.getRetirementDate(), { months: 1 }); |
|
|
const yearsToRetire = lastPeriodDate.getFullYear() - currentYear; |
|
|
const yearsToRetire = lastPeriodDate.getFullYear() - currentYear; |
|
|
|
|
|
|
|
|
@ -371,13 +383,13 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
labels.push(year); |
|
|
labels.push(year); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const datasetDeposit = { |
|
|
const datasetDeposit: ChartDataset<'bar'> = { |
|
|
backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`, |
|
|
backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`, |
|
|
data: [], |
|
|
data: [], |
|
|
label: $localize`Deposit` |
|
|
label: $localize`Deposit` |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const datasetInterest = { |
|
|
const datasetInterest: ChartDataset<'bar'> = { |
|
|
backgroundColor: Color( |
|
|
backgroundColor: Color( |
|
|
`rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})` |
|
|
`rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})` |
|
|
) |
|
|
) |
|
|
@ -387,7 +399,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
label: $localize`Interest` |
|
|
label: $localize`Interest` |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const datasetSavings = { |
|
|
const datasetSavings: ChartDataset<'bar'> = { |
|
|
backgroundColor: Color( |
|
|
backgroundColor: Color( |
|
|
`rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})` |
|
|
`rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})` |
|
|
) |
|
|
) |
|
|
@ -426,12 +438,12 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private getPeriodsToRetire(): number { |
|
|
private getPeriodsToRetire(): number { |
|
|
if (this.calculatorForm.get('projectedTotalAmount').value) { |
|
|
if (this.calculatorForm.get('projectedTotalAmount')?.value) { |
|
|
let periods = this.fireCalculatorService.calculatePeriodsToRetire({ |
|
|
let periods = this.fireCalculatorService.calculatePeriodsToRetire({ |
|
|
P: this.getP(), |
|
|
P: this.getP(), |
|
|
PMT: this.getPMT(), |
|
|
PMT: this.getPMT(), |
|
|
r: this.getR(), |
|
|
r: this.getR(), |
|
|
totalAmount: this.calculatorForm.get('projectedTotalAmount').value |
|
|
totalAmount: this.calculatorForm.get('projectedTotalAmount')!.value |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
if (periods === Infinity) { |
|
|
if (periods === Infinity) { |
|
|
@ -452,13 +464,13 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private getPMT() { |
|
|
private getPMT(): number { |
|
|
return this.calculatorForm.get('paymentPerPeriod').value; |
|
|
return this.calculatorForm.get('paymentPerPeriod')!.value; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private getProjectedTotalAmount() { |
|
|
private getProjectedTotalAmount() { |
|
|
if (this.calculatorForm.get('projectedTotalAmount').value) { |
|
|
if (this.calculatorForm.get('projectedTotalAmount')?.value) { |
|
|
return this.calculatorForm.get('projectedTotalAmount').value; |
|
|
return this.calculatorForm.get('projectedTotalAmount')!.value; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const { totalAmount } = |
|
|
const { totalAmount } = |
|
|
@ -473,10 +485,10 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private getR() { |
|
|
private getR() { |
|
|
return this.calculatorForm.get('annualInterestRate').value / 100; |
|
|
return this.calculatorForm.get('annualInterestRate')!.value / 100; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private getRetirementDate(): Date { |
|
|
private getRetirementDate(): Date | undefined { |
|
|
if (this.periodsToRetire === Number.MAX_SAFE_INTEGER) { |
|
|
if (this.periodsToRetire === Number.MAX_SAFE_INTEGER) { |
|
|
return undefined; |
|
|
return undefined; |
|
|
} |
|
|
} |
|
|
|