mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
8.0 KiB
288 lines
8.0 KiB
import {
|
|
ChangeDetectionStrategy,
|
|
ChangeDetectorRef,
|
|
Component,
|
|
Inject,
|
|
OnDestroy,
|
|
OnInit
|
|
} from '@angular/core';
|
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
|
import { Sort, SortDirection } from '@angular/material/sort';
|
|
import { MatTableDataSource } from '@angular/material/table';
|
|
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 { downloadAsFile } from '@ghostfolio/common/helper';
|
|
import {
|
|
AccountBalancesResponse,
|
|
HistoricalDataItem,
|
|
PortfolioPosition,
|
|
User
|
|
} from '@ghostfolio/common/interfaces';
|
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
|
import { OrderWithAccount } from '@ghostfolio/common/types';
|
|
import Big from 'big.js';
|
|
import { format, parseISO } from 'date-fns';
|
|
import { isNumber } from 'lodash';
|
|
import { Subject } from 'rxjs';
|
|
import { takeUntil } from 'rxjs/operators';
|
|
|
|
import { AccountDetailDialogParams } from './interfaces/interfaces';
|
|
|
|
@Component({
|
|
host: { class: 'd-flex flex-column h-100' },
|
|
selector: 'gf-account-detail-dialog',
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
templateUrl: 'account-detail-dialog.html',
|
|
styleUrls: ['./account-detail-dialog.component.scss']
|
|
})
|
|
export class AccountDetailDialog implements OnDestroy, OnInit {
|
|
public accountBalances: AccountBalancesResponse['balances'];
|
|
public activities: OrderWithAccount[];
|
|
public balance: number;
|
|
public currency: string;
|
|
public dataSource: MatTableDataSource<OrderWithAccount>;
|
|
public equity: number;
|
|
public hasImpersonationId: boolean;
|
|
public hasPermissionToDeleteAccountBalance: boolean;
|
|
public historicalDataItems: HistoricalDataItem[];
|
|
public holdings: PortfolioPosition[];
|
|
public isLoadingActivities: boolean;
|
|
public isLoadingChart: boolean;
|
|
public name: string;
|
|
public platformName: string;
|
|
public sortColumn = 'date';
|
|
public sortDirection: SortDirection = 'desc';
|
|
public totalItems: number;
|
|
public transactionCount: number;
|
|
public user: User;
|
|
public valueInBaseCurrency: number;
|
|
|
|
private unsubscribeSubject = new Subject<void>();
|
|
|
|
public constructor(
|
|
private changeDetectorRef: ChangeDetectorRef,
|
|
@Inject(MAT_DIALOG_DATA) public data: AccountDetailDialogParams,
|
|
private dataService: DataService,
|
|
public dialogRef: MatDialogRef<AccountDetailDialog>,
|
|
private impersonationStorageService: ImpersonationStorageService,
|
|
private userService: UserService
|
|
) {
|
|
this.userService.stateChanged
|
|
.pipe(takeUntil(this.unsubscribeSubject))
|
|
.subscribe((state) => {
|
|
if (state?.user) {
|
|
this.user = state.user;
|
|
|
|
this.hasPermissionToDeleteAccountBalance = hasPermission(
|
|
this.user.permissions,
|
|
permissions.deleteAccountBalance
|
|
);
|
|
|
|
this.changeDetectorRef.markForCheck();
|
|
}
|
|
});
|
|
}
|
|
|
|
public ngOnInit() {
|
|
this.dataService
|
|
.fetchAccount(this.data.accountId)
|
|
.pipe(takeUntil(this.unsubscribeSubject))
|
|
.subscribe(
|
|
({
|
|
balance,
|
|
currency,
|
|
name,
|
|
Platform,
|
|
transactionCount,
|
|
value,
|
|
valueInBaseCurrency
|
|
}) => {
|
|
this.balance = balance;
|
|
this.currency = currency;
|
|
|
|
if (isNumber(balance) && isNumber(value)) {
|
|
this.equity = new Big(value).minus(balance).toNumber();
|
|
} else {
|
|
this.equity = null;
|
|
}
|
|
|
|
this.name = name;
|
|
this.platformName = Platform?.name ?? '-';
|
|
this.transactionCount = transactionCount;
|
|
this.valueInBaseCurrency = valueInBaseCurrency;
|
|
|
|
this.changeDetectorRef.markForCheck();
|
|
}
|
|
);
|
|
|
|
this.dataService
|
|
.fetchPortfolioDetails({
|
|
filters: [
|
|
{
|
|
type: 'ACCOUNT',
|
|
id: this.data.accountId
|
|
}
|
|
]
|
|
})
|
|
.pipe(takeUntil(this.unsubscribeSubject))
|
|
.subscribe(({ holdings }) => {
|
|
this.holdings = [];
|
|
|
|
for (const [symbol, holding] of Object.entries(holdings)) {
|
|
this.holdings.push(holding);
|
|
}
|
|
|
|
this.changeDetectorRef.markForCheck();
|
|
});
|
|
|
|
this.impersonationStorageService
|
|
.onChangeHasImpersonation()
|
|
.pipe(takeUntil(this.unsubscribeSubject))
|
|
.subscribe((impersonationId) => {
|
|
this.hasImpersonationId = !!impersonationId;
|
|
});
|
|
|
|
this.fetchAccountBalances();
|
|
this.fetchActivities();
|
|
this.fetchPortfolioPerformance();
|
|
}
|
|
|
|
public onClose() {
|
|
this.dialogRef.close();
|
|
}
|
|
|
|
public onDeleteAccountBalance(aId: string) {
|
|
this.dataService
|
|
.deleteAccountBalance(aId)
|
|
.pipe(takeUntil(this.unsubscribeSubject))
|
|
.subscribe({
|
|
next: () => {
|
|
this.fetchAccountBalances();
|
|
this.fetchPortfolioPerformance();
|
|
}
|
|
});
|
|
}
|
|
|
|
public onExport() {
|
|
let activityIds = [];
|
|
|
|
if (this.user?.settings?.isExperimentalFeatures === true) {
|
|
activityIds = this.dataSource.data.map(({ id }) => {
|
|
return id;
|
|
});
|
|
} else {
|
|
activityIds = this.activities.map(({ id }) => {
|
|
return id;
|
|
});
|
|
}
|
|
|
|
this.dataService
|
|
.fetchExport({ activityIds })
|
|
.pipe(takeUntil(this.unsubscribeSubject))
|
|
.subscribe((data) => {
|
|
downloadAsFile({
|
|
content: data,
|
|
fileName: `ghostfolio-export-${this.name
|
|
.replace(/\s+/g, '-')
|
|
.toLowerCase()}-${format(
|
|
parseISO(data.meta.date),
|
|
'yyyyMMddHHmm'
|
|
)}.json`,
|
|
format: 'json'
|
|
});
|
|
});
|
|
}
|
|
|
|
public onSortChanged({ active, direction }: Sort) {
|
|
this.sortColumn = active;
|
|
this.sortDirection = direction;
|
|
|
|
this.fetchActivities();
|
|
}
|
|
|
|
private fetchAccountBalances() {
|
|
this.dataService
|
|
.fetchAccountBalances(this.data.accountId)
|
|
.pipe(takeUntil(this.unsubscribeSubject))
|
|
.subscribe(({ balances }) => {
|
|
this.accountBalances = balances;
|
|
|
|
this.changeDetectorRef.markForCheck();
|
|
});
|
|
}
|
|
|
|
private fetchActivities() {
|
|
this.isLoadingActivities = true;
|
|
|
|
if (this.user?.settings?.isExperimentalFeatures === true) {
|
|
this.dataService
|
|
.fetchActivities({
|
|
filters: [{ id: this.data.accountId, type: 'ACCOUNT' }],
|
|
sortColumn: this.sortColumn,
|
|
sortDirection: this.sortDirection
|
|
})
|
|
.pipe(takeUntil(this.unsubscribeSubject))
|
|
.subscribe(({ activities, count }) => {
|
|
this.dataSource = new MatTableDataSource(activities);
|
|
this.totalItems = count;
|
|
|
|
this.isLoadingActivities = false;
|
|
|
|
this.changeDetectorRef.markForCheck();
|
|
});
|
|
} else {
|
|
this.dataService
|
|
.fetchActivities({
|
|
filters: [{ id: this.data.accountId, type: 'ACCOUNT' }]
|
|
})
|
|
.pipe(takeUntil(this.unsubscribeSubject))
|
|
.subscribe(({ activities }) => {
|
|
this.activities = activities;
|
|
|
|
this.isLoadingActivities = false;
|
|
|
|
this.changeDetectorRef.markForCheck();
|
|
});
|
|
}
|
|
}
|
|
|
|
private fetchPortfolioPerformance() {
|
|
this.isLoadingChart = true;
|
|
|
|
this.dataService
|
|
.fetchPortfolioPerformance({
|
|
filters: [
|
|
{
|
|
id: this.data.accountId,
|
|
type: 'ACCOUNT'
|
|
}
|
|
],
|
|
range: 'max',
|
|
withExcludedAccounts: true
|
|
})
|
|
.pipe(takeUntil(this.unsubscribeSubject))
|
|
.subscribe(({ chart }) => {
|
|
this.historicalDataItems = chart.map(
|
|
({ date, netWorth, netWorthInPercentage }) => {
|
|
return {
|
|
date,
|
|
value:
|
|
this.hasImpersonationId || this.user.settings.isRestrictedView
|
|
? netWorthInPercentage
|
|
: netWorth
|
|
};
|
|
}
|
|
);
|
|
|
|
this.isLoadingChart = false;
|
|
|
|
this.changeDetectorRef.markForCheck();
|
|
});
|
|
}
|
|
|
|
public ngOnDestroy() {
|
|
this.unsubscribeSubject.next();
|
|
this.unsubscribeSubject.complete();
|
|
}
|
|
}
|
|
|