Feature/add summary row to activities table (#645)
* Add summary row to activities table * Update changelog
This commit is contained in:
parent
9d907b5eb5
commit
585f99e4df
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Added the footer row with total fees and total value to the activities table
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgraded _Stripe_ dependencies
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { CacheService } from '@ghostfolio/api/app/cache/cache.service';
|
||||
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
||||
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
|
||||
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
||||
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
|
||||
@ -15,10 +15,11 @@ import { ImportService } from './import.service';
|
||||
ConfigurationModule,
|
||||
DataGatheringModule,
|
||||
DataProviderModule,
|
||||
OrderModule,
|
||||
PrismaModule,
|
||||
RedisCacheModule
|
||||
],
|
||||
controllers: [ImportController],
|
||||
providers: [CacheService, ImportService, OrderService]
|
||||
providers: [CacheService, ImportService]
|
||||
})
|
||||
export class ImportModule {}
|
||||
|
10
apps/api/src/app/order/interfaces/activities.interface.ts
Normal file
10
apps/api/src/app/order/interfaces/activities.interface.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { OrderWithAccount } from '@ghostfolio/common/types';
|
||||
|
||||
export interface Activities {
|
||||
activities: Activity[];
|
||||
}
|
||||
|
||||
export interface Activity extends OrderWithAccount {
|
||||
feeInBaseCurrency: number;
|
||||
valueInBaseCurrency: number;
|
||||
}
|
@ -23,6 +23,7 @@ import { parseISO } from 'date-fns';
|
||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||
|
||||
import { CreateOrderDto } from './create-order.dto';
|
||||
import { Activities } from './interfaces/activities.interface';
|
||||
import { OrderService } from './order.service';
|
||||
import { UpdateOrderDto } from './update-order.dto';
|
||||
|
||||
@ -59,14 +60,16 @@ export class OrderController {
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
public async getAllOrders(
|
||||
@Headers('impersonation-id') impersonationId
|
||||
): Promise<OrderModel[]> {
|
||||
): Promise<Activities> {
|
||||
const impersonationUserId =
|
||||
await this.impersonationService.validateImpersonationId(
|
||||
impersonationId,
|
||||
this.request.user.id
|
||||
);
|
||||
const userCurrency = this.request.user.Settings.currency;
|
||||
|
||||
let orders = await this.orderService.getOrders({
|
||||
let activities = await this.orderService.getOrders({
|
||||
userCurrency,
|
||||
includeDrafts: true,
|
||||
userId: impersonationUserId || this.request.user.id
|
||||
});
|
||||
@ -75,15 +78,17 @@ export class OrderController {
|
||||
impersonationUserId ||
|
||||
this.userService.isRestrictedView(this.request.user)
|
||||
) {
|
||||
orders = nullifyValuesInObjects(orders, [
|
||||
activities = nullifyValuesInObjects(activities, [
|
||||
'fee',
|
||||
'feeInBaseCurrency',
|
||||
'quantity',
|
||||
'unitPrice',
|
||||
'value'
|
||||
'value',
|
||||
'valueInBaseCurrency'
|
||||
]);
|
||||
}
|
||||
|
||||
return orders;
|
||||
return { activities };
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
|
@ -4,6 +4,7 @@ import { UserModule } from '@ghostfolio/api/app/user/user.module';
|
||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
||||
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
|
||||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module';
|
||||
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation.module';
|
||||
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
|
||||
import { Module } from '@nestjs/common';
|
||||
@ -16,6 +17,7 @@ import { OrderService } from './order.service';
|
||||
ConfigurationModule,
|
||||
DataGatheringModule,
|
||||
DataProviderModule,
|
||||
ExchangeRateDataModule,
|
||||
ImpersonationModule,
|
||||
PrismaModule,
|
||||
RedisCacheModule,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { CacheService } from '@ghostfolio/api/app/cache/cache.service';
|
||||
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
|
||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { OrderWithAccount } from '@ghostfolio/common/types';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
@ -7,10 +8,13 @@ import { DataSource, Order, Prisma, Type as TypeOfOrder } from '@prisma/client';
|
||||
import Big from 'big.js';
|
||||
import { endOfToday, isAfter } from 'date-fns';
|
||||
|
||||
import { Activity } from './interfaces/activities.interface';
|
||||
|
||||
@Injectable()
|
||||
export class OrderService {
|
||||
public constructor(
|
||||
private readonly cacheService: CacheService,
|
||||
private readonly exchangeRateDataService: ExchangeRateDataService,
|
||||
private readonly dataGatheringService: DataGatheringService,
|
||||
private readonly prismaService: PrismaService
|
||||
) {}
|
||||
@ -86,12 +90,14 @@ export class OrderService {
|
||||
public async getOrders({
|
||||
includeDrafts = false,
|
||||
types,
|
||||
userCurrency,
|
||||
userId
|
||||
}: {
|
||||
includeDrafts?: boolean;
|
||||
types?: TypeOfOrder[];
|
||||
userCurrency: string;
|
||||
userId: string;
|
||||
}) {
|
||||
}): Promise<Activity[]> {
|
||||
const where: Prisma.OrderWhereInput = { userId };
|
||||
|
||||
if (includeDrafts === false) {
|
||||
@ -124,12 +130,21 @@ export class OrderService {
|
||||
orderBy: { date: 'asc' }
|
||||
})
|
||||
).map((order) => {
|
||||
const value = new Big(order.quantity).mul(order.unitPrice).toNumber();
|
||||
|
||||
return {
|
||||
...order,
|
||||
value: new Big(order.quantity)
|
||||
.mul(order.unitPrice)
|
||||
.plus(order.fee)
|
||||
.toNumber()
|
||||
value,
|
||||
feeInBaseCurrency: this.exchangeRateDataService.toCurrency(
|
||||
order.fee,
|
||||
order.currency,
|
||||
userCurrency
|
||||
),
|
||||
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
|
||||
value,
|
||||
order.currency,
|
||||
userCurrency
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -388,11 +388,12 @@ export class PortfolioService {
|
||||
aImpersonationId: string,
|
||||
aSymbol: string
|
||||
): Promise<PortfolioPositionDetail> {
|
||||
const userCurrency = this.request.user.Settings.currency;
|
||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
||||
|
||||
const orders = (await this.orderService.getOrders({ userId })).filter(
|
||||
(order) => order.symbol === aSymbol
|
||||
);
|
||||
const orders = (
|
||||
await this.orderService.getOrders({ userCurrency, userId })
|
||||
).filter((order) => order.symbol === aSymbol);
|
||||
|
||||
if (orders.length <= 0) {
|
||||
return {
|
||||
@ -846,24 +847,25 @@ export class PortfolioService {
|
||||
}
|
||||
|
||||
public async getSummary(aImpersonationId: string): Promise<PortfolioSummary> {
|
||||
const currency = this.request.user.Settings.currency;
|
||||
const userCurrency = this.request.user.Settings.currency;
|
||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
||||
|
||||
const performanceInformation = await this.getPerformance(aImpersonationId);
|
||||
|
||||
const { balance } = await this.accountService.getCashDetails(
|
||||
userId,
|
||||
currency
|
||||
userCurrency
|
||||
);
|
||||
const orders = await this.orderService.getOrders({
|
||||
userCurrency,
|
||||
userId
|
||||
});
|
||||
const dividend = this.getDividend(orders).toNumber();
|
||||
const fees = this.getFees(orders).toNumber();
|
||||
const firstOrderDate = orders[0]?.date;
|
||||
|
||||
const totalBuy = this.getTotalByType(orders, currency, 'BUY');
|
||||
const totalSell = this.getTotalByType(orders, currency, 'SELL');
|
||||
const totalBuy = this.getTotalByType(orders, userCurrency, 'BUY');
|
||||
const totalSell = this.getTotalByType(orders, userCurrency, 'SELL');
|
||||
|
||||
const committedFunds = new Big(totalBuy).sub(totalSell);
|
||||
|
||||
@ -895,8 +897,8 @@ export class PortfolioService {
|
||||
}: {
|
||||
cashDetails: CashDetails;
|
||||
investment: Big;
|
||||
value: Big;
|
||||
userCurrency: string;
|
||||
value: Big;
|
||||
}) {
|
||||
const cashPositions = {};
|
||||
|
||||
@ -1025,8 +1027,11 @@ export class PortfolioService {
|
||||
transactionPoints: TransactionPoint[];
|
||||
orders: OrderWithAccount[];
|
||||
}> {
|
||||
const userCurrency = this.request.user?.Settings?.currency ?? baseCurrency;
|
||||
|
||||
const orders = await this.orderService.getOrders({
|
||||
includeDrafts,
|
||||
userCurrency,
|
||||
userId,
|
||||
types: ['BUY', 'SELL']
|
||||
});
|
||||
@ -1035,7 +1040,6 @@ export class PortfolioService {
|
||||
return { transactionPoints: [], orders: [] };
|
||||
}
|
||||
|
||||
const userCurrency = this.request.user?.Settings?.currency ?? baseCurrency;
|
||||
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
|
||||
currency: order.currency,
|
||||
dataSource: order.SymbolProfile?.dataSource ?? order.dataSource,
|
||||
|
@ -15,7 +15,7 @@
|
||||
>(Default)</span
|
||||
>
|
||||
</td>
|
||||
<td *matFooterCellDef class="px-1" mat-footer-cell>Total</td>
|
||||
<td *matFooterCellDef class="px-1" mat-footer-cell i18n>Total</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="currency">
|
||||
|
@ -3,6 +3,7 @@ import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
|
||||
import { PositionDetailDialog } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.component';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
@ -28,6 +29,7 @@ import { ImportTransactionDialog } from './import-transaction-dialog/import-tran
|
||||
templateUrl: './transactions-page.html'
|
||||
})
|
||||
export class TransactionsPageComponent implements OnDestroy, OnInit {
|
||||
public activities: Activity[];
|
||||
public defaultAccountId: string;
|
||||
public deviceType: string;
|
||||
public hasImpersonationId: boolean;
|
||||
@ -35,7 +37,6 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
||||
public hasPermissionToDeleteOrder: boolean;
|
||||
public hasPermissionToImportOrders: boolean;
|
||||
public routeQueryParams: Subscription;
|
||||
public transactions: OrderModel[];
|
||||
public user: User;
|
||||
|
||||
private primaryDataSource: DataSource;
|
||||
@ -65,8 +66,8 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
||||
if (params['createDialog']) {
|
||||
this.openCreateTransactionDialog();
|
||||
} else if (params['editDialog']) {
|
||||
if (this.transactions) {
|
||||
const transaction = this.transactions.find(({ id }) => {
|
||||
if (this.activities) {
|
||||
const transaction = this.activities.find(({ id }) => {
|
||||
return id === params['transactionId'];
|
||||
});
|
||||
|
||||
@ -119,10 +120,10 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
||||
this.dataService
|
||||
.fetchOrders()
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((response) => {
|
||||
this.transactions = response;
|
||||
.subscribe(({ activities }) => {
|
||||
this.activities = activities;
|
||||
|
||||
if (this.hasPermissionToCreateOrder && this.transactions?.length <= 0) {
|
||||
if (this.hasPermissionToCreateOrder && this.activities?.length <= 0) {
|
||||
this.router.navigate([], { queryParams: { createDialog: true } });
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="col">
|
||||
<h3 class="d-flex justify-content-center mb-3" i18n>Activities</h3>
|
||||
<gf-activities-table
|
||||
[activities]="transactions"
|
||||
[activities]="activities"
|
||||
[baseCurrency]="user?.settings?.baseCurrency"
|
||||
[deviceType]="deviceType"
|
||||
[hasPermissionToCreateActivity]="hasPermissionToCreateOrder"
|
||||
|
@ -4,6 +4,7 @@ import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto';
|
||||
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
|
||||
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';
|
||||
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
|
||||
import { Activities } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
|
||||
import { PortfolioPositions } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-positions.interface';
|
||||
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
|
||||
@ -169,14 +170,14 @@ export class DataService {
|
||||
);
|
||||
}
|
||||
|
||||
public fetchOrders(): Observable<OrderModel[]> {
|
||||
return this.http.get<any[]>('/api/order').pipe(
|
||||
map((data) => {
|
||||
for (const item of data) {
|
||||
item.createdAt = parseISO(item.createdAt);
|
||||
item.date = parseISO(item.date);
|
||||
public fetchOrders(): Observable<Activities> {
|
||||
return this.http.get<any>('/api/order').pipe(
|
||||
map(({ activities }) => {
|
||||
for (const activity of activities) {
|
||||
activity.createdAt = parseISO(activity.createdAt);
|
||||
activity.date = parseISO(activity.date);
|
||||
}
|
||||
return data;
|
||||
return { activities };
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -58,6 +58,11 @@
|
||||
>
|
||||
{{ dataSource.data.length - i }}
|
||||
</td>
|
||||
<td
|
||||
*matFooterCellDef
|
||||
class="d-none d-lg-table-cell px-1"
|
||||
mat-footer-cell
|
||||
></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="date">
|
||||
<th *matHeaderCellDef class="px-1" i18n mat-header-cell mat-sort-header>
|
||||
@ -68,6 +73,7 @@
|
||||
{{ element.date | date: defaultDateFormat }}
|
||||
</div>
|
||||
</td>
|
||||
<td *matFooterCellDef class="px-1" i18n mat-footer-cell>Total</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="type">
|
||||
@ -93,6 +99,7 @@
|
||||
<span class="d-none d-lg-block mx-1">{{ element.type }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="symbol">
|
||||
@ -107,6 +114,7 @@
|
||||
>
|
||||
</div>
|
||||
</td>
|
||||
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="currency">
|
||||
@ -122,6 +130,9 @@
|
||||
<td *matCellDef="let element" class="d-none d-lg-table-cell px-1" mat-cell>
|
||||
{{ element.currency }}
|
||||
</td>
|
||||
<td *matFooterCellDef class="d-none d-lg-table-cell px-1" mat-footer-cell>
|
||||
{{ baseCurrency }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="quantity">
|
||||
@ -143,6 +154,11 @@
|
||||
></gf-value>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
*matFooterCellDef
|
||||
class="d-none d-lg-table-cell px-1"
|
||||
mat-footer-cell
|
||||
></td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="unitPrice">
|
||||
@ -164,6 +180,11 @@
|
||||
></gf-value>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
*matFooterCellDef
|
||||
class="d-none d-lg-table-cell px-1"
|
||||
mat-footer-cell
|
||||
></td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="fee">
|
||||
@ -176,7 +197,7 @@
|
||||
>
|
||||
Fee
|
||||
</th>
|
||||
<td *matCellDef="let element" class="d-none d-lg-table-cell px1" mat-cell>
|
||||
<td *matCellDef="let element" class="d-none d-lg-table-cell px-1" mat-cell>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
[isCurrency]="true"
|
||||
@ -185,6 +206,15 @@
|
||||
></gf-value>
|
||||
</div>
|
||||
</td>
|
||||
<td *matFooterCellDef class="d-none d-lg-table-cell px-1" mat-footer-cell>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
[isCurrency]="true"
|
||||
[locale]="locale"
|
||||
[value]="isLoading ? undefined : totalFees"
|
||||
></gf-value>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="value">
|
||||
@ -197,7 +227,7 @@
|
||||
>
|
||||
Value
|
||||
</th>
|
||||
<td *matCellDef="let element" class="px1" mat-cell>
|
||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
[isCurrency]="true"
|
||||
@ -206,6 +236,15 @@
|
||||
></gf-value>
|
||||
</div>
|
||||
</td>
|
||||
<td *matFooterCellDef class="px-1" mat-footer-cell>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
[isCurrency]="true"
|
||||
[locale]="locale"
|
||||
[value]="isLoading ? undefined : totalValue"
|
||||
></gf-value>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="account">
|
||||
@ -223,6 +262,11 @@
|
||||
<span class="d-none d-lg-block">{{ element.Account?.name }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
*matFooterCellDef
|
||||
class="d-none d-lg-table-cell px-1"
|
||||
mat-footer-cell
|
||||
></td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
@ -276,6 +320,7 @@
|
||||
</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
<td *matFooterCellDef class="px-1" mat-footer-cell></td>
|
||||
</ng-container>
|
||||
|
||||
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
|
||||
@ -291,6 +336,11 @@
|
||||
"
|
||||
[ngClass]="{ 'cursor-pointer': hasPermissionToOpenDetails && !row.isDraft }"
|
||||
></tr>
|
||||
<tr
|
||||
*matFooterRowDef="displayedColumns"
|
||||
mat-footer-row
|
||||
[ngClass]="{ 'd-none': isLoading || dataSource.data.length === 0 }"
|
||||
></tr>
|
||||
</table>
|
||||
|
||||
<ngx-skeleton-loader
|
||||
|
@ -15,6 +15,16 @@
|
||||
}
|
||||
|
||||
.mat-table {
|
||||
td {
|
||||
&.mat-footer-cell {
|
||||
border-top: 1px solid
|
||||
rgba(
|
||||
var(--palette-foreground-divider),
|
||||
var(--palette-foreground-divider-alpha)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
::ng-deep {
|
||||
.mat-sort-header-container {
|
||||
@ -55,6 +65,15 @@
|
||||
}
|
||||
|
||||
.mat-table {
|
||||
td {
|
||||
&.mat-footer-cell {
|
||||
border-top-color: rgba(
|
||||
var(--palette-foreground-divider-dark),
|
||||
var(--palette-foreground-divider-dark-alpha)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.type-badge {
|
||||
background-color: rgba(
|
||||
var(--palette-foreground-text-dark),
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
@ -20,9 +19,12 @@ import { MatChipInputEvent } from '@angular/material/chips';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { Router } from '@angular/router';
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
|
||||
import { OrderWithAccount } from '@ghostfolio/common/types';
|
||||
import Big from 'big.js';
|
||||
import { endOfToday, format, isAfter } from 'date-fns';
|
||||
import { isNumber } from 'lodash';
|
||||
import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@ -36,7 +38,7 @@ const SEARCH_STRING_SEPARATOR = ',';
|
||||
templateUrl: './activities-table.component.html'
|
||||
})
|
||||
export class ActivitiesTableComponent implements OnChanges, OnDestroy {
|
||||
@Input() activities: OrderWithAccount[];
|
||||
@Input() activities: Activity[];
|
||||
@Input() baseCurrency: string;
|
||||
@Input() deviceType: string;
|
||||
@Input() hasPermissionToCreateActivity: boolean;
|
||||
@ -57,8 +59,7 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy {
|
||||
@ViewChild('searchInput') searchInput: ElementRef<HTMLInputElement>;
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
|
||||
public dataSource: MatTableDataSource<OrderWithAccount> =
|
||||
new MatTableDataSource();
|
||||
public dataSource: MatTableDataSource<Activity> = new MatTableDataSource();
|
||||
public defaultDateFormat = DEFAULT_DATE_FORMAT;
|
||||
public displayedColumns = [];
|
||||
public endOfToday = endOfToday();
|
||||
@ -71,6 +72,8 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy {
|
||||
public searchControl = new FormControl();
|
||||
public searchKeywords: string[] = [];
|
||||
public separatorKeysCodes: number[] = [ENTER, COMMA];
|
||||
public totalFees: number;
|
||||
public totalValue: number;
|
||||
|
||||
private allFilters: string[];
|
||||
private unsubscribeSubject = new Subject<void>();
|
||||
@ -218,6 +221,9 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy {
|
||||
);
|
||||
|
||||
this.filters$.next(this.allFilters);
|
||||
|
||||
this.totalFees = this.getTotalFees();
|
||||
this.totalValue = this.getTotalValue();
|
||||
}
|
||||
|
||||
private getSearchableFieldValues(activities: OrderWithAccount[]): string[] {
|
||||
@ -263,4 +269,36 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy {
|
||||
return item !== undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private getTotalFees() {
|
||||
let totalFees = new Big(0);
|
||||
|
||||
for (const activity of this.dataSource.filteredData) {
|
||||
if (isNumber(activity.feeInBaseCurrency)) {
|
||||
totalFees = totalFees.plus(activity.feeInBaseCurrency);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return totalFees.toNumber();
|
||||
}
|
||||
|
||||
private getTotalValue() {
|
||||
let totalValue = new Big(0);
|
||||
|
||||
for (const activity of this.dataSource.filteredData) {
|
||||
if (isNumber(activity.valueInBaseCurrency)) {
|
||||
if (activity.type === 'BUY') {
|
||||
totalValue = totalValue.plus(activity.valueInBaseCurrency);
|
||||
} else if (activity.type === 'SELL') {
|
||||
totalValue = totalValue.minus(activity.valueInBaseCurrency);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return totalValue.toNumber();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user