Feature/add support for emergency fund (#749)

* Add support for emergency fund

* Update changelog
This commit is contained in:
Thomas Kaul
2022-03-12 13:44:47 +01:00
committed by GitHub
parent 07799573cb
commit 7b6893b5ed
18 changed files with 182 additions and 17 deletions

View File

@@ -1,7 +1,9 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { PortfolioSummary, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@@ -11,6 +13,8 @@ import { takeUntil } from 'rxjs/operators';
templateUrl: './home-summary.html'
})
export class HomeSummaryComponent implements OnDestroy, OnInit {
public hasImpersonationId: boolean;
public hasPermissionToUpdateUserSettings: boolean;
public isLoading = true;
public summary: PortfolioSummary;
public user: User;
@@ -23,6 +27,7 @@ export class HomeSummaryComponent implements OnDestroy, OnInit {
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private impersonationStorageService: ImpersonationStorageService,
private userService: UserService
) {
this.userService.stateChanged
@@ -31,6 +36,11 @@ export class HomeSummaryComponent implements OnDestroy, OnInit {
if (state?.user) {
this.user = state.user;
this.hasPermissionToUpdateUserSettings = hasPermission(
this.user.permissions,
permissions.updateUserSettings
);
this.changeDetectorRef.markForCheck();
}
});
@@ -40,9 +50,25 @@ export class HomeSummaryComponent implements OnDestroy, OnInit {
* Initializes the controller
*/
public ngOnInit() {
this.impersonationStorageService
.onChangeHasImpersonation()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((aId) => {
this.hasImpersonationId = !!aId;
});
this.update();
}
public onChangeEmergencyFund(emergencyFund: number) {
this.dataService
.putUserSetting({ emergencyFund })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.update();
});
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();

View File

@@ -8,9 +8,11 @@
<mat-card-content>
<gf-portfolio-summary
[baseCurrency]="user?.settings?.baseCurrency"
[hasPermissionToUpdateUserSettings]="!hasImpersonationId && hasPermissionToUpdateUserSettings"
[isLoading]="isLoading"
[locale]="user?.settings?.locale"
[summary]="summary"
(emergencyFundChanged)="onChangeEmergencyFund($event)"
></gf-portfolio-summary>
</mat-card-content>
</mat-card>

View File

@@ -130,6 +130,26 @@
></gf-value>
</div>
</div>
<div class="row px-3 py-1">
<div class="d-flex flex-grow-1" i18n>Emergency Fund</div>
<div
class="align-items-center d-flex justify-content-end"
[ngClass]="{ 'cursor-pointer': hasPermissionToUpdateUserSettings }"
(click)="hasPermissionToUpdateUserSettings && onEditEmergencyFund()"
>
<ion-icon
*ngIf="hasPermissionToUpdateUserSettings && !isLoading"
class="mr-1 text-muted"
name="ellipsis-horizontal-circle-outline"
></ion-icon>
<gf-value
class="justify-content-end"
[currency]="baseCurrency"
[locale]="locale"
[value]="isLoading ? undefined : summary?.emergencyFund"
></gf-value>
</div>
</div>
<div class="row px-3 py-1">
<div class="d-flex flex-grow-1" i18n>Cash (Buying Power)</div>
<div class="d-flex justify-content-end">

View File

@@ -1,9 +1,11 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnChanges,
OnInit
OnInit,
Output
} from '@angular/core';
import { PortfolioSummary } from '@ghostfolio/common/interfaces';
import { formatDistanceToNow } from 'date-fns';
@@ -16,10 +18,13 @@ import { formatDistanceToNow } from 'date-fns';
})
export class PortfolioSummaryComponent implements OnChanges, OnInit {
@Input() baseCurrency: string;
@Input() hasPermissionToUpdateUserSettings: boolean;
@Input() isLoading: boolean;
@Input() locale: string;
@Input() summary: PortfolioSummary;
@Output() emergencyFundChanged = new EventEmitter<number>();
public timeInMarket: string;
public constructor() {}
@@ -37,4 +42,16 @@ export class PortfolioSummaryComponent implements OnChanges, OnInit {
this.timeInMarket = undefined;
}
}
public onEditEmergencyFund() {
const emergencyFundInput = prompt(
'Please enter the amount of your emergency fund:',
this.summary.emergencyFund.toString()
);
const emergencyFund = parseFloat(emergencyFundInput?.trim());
if (emergencyFund >= 0) {
this.emergencyFundChanged.emit(emergencyFund);
}
}
}

View File

@@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { GfValueModule } from '@ghostfolio/ui/value';
import { PortfolioSummaryComponent } from './portfolio-summary.component';
@@ -8,6 +8,6 @@ import { PortfolioSummaryComponent } from './portfolio-summary.component';
declarations: [PortfolioSummaryComponent],
exports: [PortfolioSummaryComponent],
imports: [CommonModule, GfValueModule],
providers: []
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfPortfolioSummaryModule {}

View File

@@ -13,6 +13,7 @@ import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router';
import { ASSET_SUB_CLASS_EMERGENCY_FUND } from '@ghostfolio/common/config';
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetClass, Order as OrderModel } from '@prisma/client';
import { Subject, Subscription } from 'rxjs';
@@ -39,7 +40,10 @@ export class PositionsTableComponent implements OnChanges, OnDestroy, OnInit {
public dataSource: MatTableDataSource<PortfolioPosition> =
new MatTableDataSource();
public displayedColumns = [];
public ignoreAssetSubClasses = [AssetClass.CASH.toString()];
public ignoreAssetSubClasses = [
AssetClass.CASH.toString(),
ASSET_SUB_CLASS_EMERGENCY_FUND
];
public isLoading = true;
public pageSize = 7;
public routeQueryParams: Subscription;