Feature/improve usability of fire calculator (#1779)

* Improve usability

* Add debounce
* Persist annualInterestRate
* Partially disable date picker

* Update changelog
This commit is contained in:
Thomas Kaul
2023-03-12 09:55:55 +01:00
committed by GitHub
parent 0f1db71604
commit 3af8be89e3
8 changed files with 111 additions and 58 deletions

View File

@@ -1,6 +1,7 @@
import { ColorScheme, DateRange, ViewMode } from '@ghostfolio/common/types';
export interface UserSettings {
annualInterestRate?: number;
baseCurrency?: string;
benchmark?: string;
colorScheme?: ColorScheme;

View File

@@ -30,18 +30,27 @@
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Retirement Date</mat-label>
<div>
{{
calculatorForm.controls['retirementDate'].value
| date : 'MMMM YYYY'
}}
</div>
<input
class="d-none"
formControlName="retirementDate"
matInput
[matDatepicker]="datepicker"
/>
<mat-datepicker-toggle
matIconSuffix
[disabled]="hasPermissionToUpdateUserSettings !== true"
[for]="datepicker"
></mat-datepicker-toggle>
<mat-datepicker
#datepicker
startView="multi-year"
[disabled]="hasPermissionToUpdateUserSettings !== true"
(monthSelected)="setMonthAndYear($event, datepicker)"
>
</mat-datepicker>

View File

@@ -8,4 +8,31 @@
height: 100%;
}
}
::ng-deep {
.mdc-text-field--disabled {
.mdc-floating-label,
.mdc-text-field__input {
color: inherit;
}
.mdc-notched-outline__leading,
.mdc-notched-outline__notch,
.mdc-notched-outline__trailing {
border-color: rgba(var(--dark-disabled-text));
}
}
}
}
:host-context(.is-dark-theme) {
::ng-deep {
.mdc-text-field--disabled {
.mdc-notched-outline__leading,
.mdc-notched-outline__notch,
.mdc-notched-outline__trailing {
border-color: rgba(var(--dark-disabled-text));
}
}
}
}

View File

@@ -1,7 +1,6 @@
import 'chartjs-adapter-date-fns';
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
@@ -39,7 +38,7 @@ import {
sub
} from 'date-fns';
import { isNumber } from 'lodash';
import { Subject, takeUntil } from 'rxjs';
import { debounceTime, Subject, takeUntil } from 'rxjs';
import { FireCalculatorService } from './fire-calculator.service';
@@ -49,9 +48,8 @@ import { FireCalculatorService } from './fire-calculator.service';
styleUrls: ['./fire-calculator.component.scss'],
templateUrl: './fire-calculator.component.html'
})
export class FireCalculatorComponent
implements AfterViewInit, OnChanges, OnDestroy
{
export class FireCalculatorComponent implements OnChanges, OnDestroy {
@Input() annualInterestRate = 5;
@Input() colorScheme: ColorScheme;
@Input() currency: string;
@Input() deviceType: string;
@@ -62,6 +60,7 @@ export class FireCalculatorComponent
@Input() retirementDate: Date;
@Input() savingsRate = 0;
@Output() annualInterestRateChanged = new EventEmitter<number>();
@Output() projectedTotalAmountChanged = new EventEmitter<number>();
@Output() retirementDateChanged = new EventEmitter<Date>();
@Output() savingsRateChanged = new EventEmitter<number>();
@@ -100,7 +99,7 @@ export class FireCalculatorComponent
this.calculatorForm.setValue(
{
annualInterestRate: 5,
annualInterestRate: this.annualInterestRate,
paymentPerPeriod: this.savingsRate,
principalInvestmentAmount: 0,
projectedTotalAmount: this.projectedTotalAmount,
@@ -117,75 +116,45 @@ export class FireCalculatorComponent
this.initialize();
});
this.calculatorForm
.get('annualInterestRate')
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject))
.subscribe((annualInterestRate) => {
this.annualInterestRateChanged.emit(annualInterestRate);
});
this.calculatorForm
.get('paymentPerPeriod')
.valueChanges.pipe(takeUntil(this.unsubscribeSubject))
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject))
.subscribe((savingsRate) => {
this.savingsRateChanged.emit(savingsRate);
});
this.calculatorForm
.get('projectedTotalAmount')
.valueChanges.pipe(takeUntil(this.unsubscribeSubject))
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject))
.subscribe((projectedTotalAmount) => {
this.projectedTotalAmountChanged.emit(projectedTotalAmount);
});
this.calculatorForm
.get('retirementDate')
.valueChanges.pipe(takeUntil(this.unsubscribeSubject))
.valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject))
.subscribe((retirementDate) => {
this.retirementDateChanged.emit(retirementDate);
});
}
public ngAfterViewInit() {
if (isNumber(this.fireWealth) && this.fireWealth >= 0) {
setTimeout(() => {
// Wait for the chartCanvas
this.calculatorForm.patchValue(
{
paymentPerPeriod: this.getPMT(),
principalInvestmentAmount: this.getP(),
projectedTotalAmount:
Number(this.getProjectedTotalAmount().toFixed(2)) ?? 0,
retirementDate:
this.getRetirementDate() ?? this.DEFAULT_RETIREMENT_DATE
},
{
emitEvent: false
}
);
this.calculatorForm.get('principalInvestmentAmount').disable();
this.changeDetectorRef.markForCheck();
});
}
if (this.hasPermissionToUpdateUserSettings === true) {
this.calculatorForm.get('paymentPerPeriod').enable({ emitEvent: false });
this.calculatorForm
.get('projectedTotalAmount')
.enable({ emitEvent: false });
this.calculatorForm.get('retirementDate').enable({ emitEvent: false });
} else {
this.calculatorForm.get('paymentPerPeriod').disable({ emitEvent: false });
this.calculatorForm
.get('projectedTotalAmount')
.disable({ emitEvent: false });
this.calculatorForm.get('retirementDate').disable({ emitEvent: false });
}
}
public ngOnChanges() {
this.periodsToRetire = this.getPeriodsToRetire();
if (isNumber(this.fireWealth) && this.fireWealth >= 0) {
setTimeout(() => {
// Wait for the chartCanvas
this.calculatorForm.patchValue(
{
annualInterestRate: this.annualInterestRate,
principalInvestmentAmount: this.fireWealth,
paymentPerPeriod: this.savingsRate ?? 0,
projectedTotalAmount:
Number(this.getProjectedTotalAmount().toFixed(2)) ?? 0,
Number(this.getProjectedTotalAmount().toFixed(0)) ?? 0,
retirementDate:
this.getRetirementDate() ?? this.DEFAULT_RETIREMENT_DATE
},
@@ -200,18 +169,24 @@ export class FireCalculatorComponent
}
if (this.hasPermissionToUpdateUserSettings === true) {
this.calculatorForm
.get('annualInterestRate')
.enable({ emitEvent: false });
this.calculatorForm.get('paymentPerPeriod').enable({ emitEvent: false });
this.calculatorForm
.get('projectedTotalAmount')
.enable({ emitEvent: false });
this.calculatorForm.get('retirementDate').enable({ emitEvent: false });
} else {
this.calculatorForm
.get('annualInterestRate')
.disable({ emitEvent: false });
this.calculatorForm.get('paymentPerPeriod').disable({ emitEvent: false });
this.calculatorForm
.get('projectedTotalAmount')
.disable({ emitEvent: false });
this.calculatorForm.get('retirementDate').disable({ emitEvent: false });
}
this.calculatorForm.get('retirementDate').disable({ emitEvent: false });
}
public setMonthAndYear(