diff --git a/CHANGELOG.md b/CHANGELOG.md index d219c45b0..4995991de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Added support for future transactions (drafts) + ## 1.22.0 - 25.06.2021 ### Added diff --git a/apps/api/src/app/order/order.controller.ts b/apps/api/src/app/order/order.controller.ts index a386898a2..5671a5c9f 100644 --- a/apps/api/src/app/order/order.controller.ts +++ b/apps/api/src/app/order/order.controller.ts @@ -68,10 +68,11 @@ export class OrderController { public async getAllOrders( @Headers('impersonation-id') impersonationId ): Promise { - const impersonationUserId = await this.impersonationService.validateImpersonationId( - impersonationId, - this.request.user.id - ); + const impersonationUserId = + await this.impersonationService.validateImpersonationId( + impersonationId, + this.request.user.id + ); let orders = await this.orderService.orders({ include: { diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index b933b054f..93fd8a706 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -3,6 +3,7 @@ import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { OrderWithAccount } from '@ghostfolio/common/types'; import { Injectable } from '@nestjs/common'; import { DataSource, Order, Prisma } from '@prisma/client'; +import { endOfToday, isAfter } from 'date-fns'; import { CacheService } from '../cache/cache.service'; import { RedisCacheService } from '../redis-cache/redis-cache.service'; @@ -50,14 +51,16 @@ export class OrderService { ): Promise { this.redisCacheService.remove(`${aUserId}.portfolio`); - // Gather symbol data of order in the background - this.dataGatheringService.gatherSymbols([ - { - dataSource: data.dataSource, - date: data.date, - symbol: data.symbol - } - ]); + if (!isAfter(data.date as Date, endOfToday())) { + // Gather symbol data of order in the background, if not draft + this.dataGatheringService.gatherSymbols([ + { + dataSource: data.dataSource, + date: data.date, + symbol: data.symbol + } + ]); + } await this.cacheService.flush(aUserId); diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index ac7c6bf32..2d4081337 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -14,6 +14,7 @@ import { REQUEST } from '@nestjs/core'; import { DataSource } from '@prisma/client'; import { add, + endOfToday, format, getDate, getMonth, @@ -52,7 +53,7 @@ export class PortfolioService { public async createPortfolio(aUserId: string): Promise { let portfolio: Portfolio; - let stringifiedPortfolio = await this.redisCacheService.get( + const stringifiedPortfolio = await this.redisCacheService.get( `${aUserId}.portfolio` ); @@ -63,9 +64,8 @@ export class PortfolioService { const { orders, portfolioItems - }: { orders: IOrder[]; portfolioItems: PortfolioItem[] } = JSON.parse( - stringifiedPortfolio - ); + }: { orders: IOrder[]; portfolioItems: PortfolioItem[] } = + JSON.parse(stringifiedPortfolio); portfolio = new Portfolio( this.dataProviderService, @@ -104,15 +104,21 @@ export class PortfolioService { } // Enrich portfolio with current data - return await portfolio.addCurrentPortfolioItems(); + await portfolio.addCurrentPortfolioItems(); + + // Enrich portfolio with future data + await portfolio.addFuturePortfolioItems(); + + return portfolio; } public async findAll(aImpersonationId: string): Promise { try { - const impersonationUserId = await this.impersonationService.validateImpersonationId( - aImpersonationId, - this.request.user.id - ); + const impersonationUserId = + await this.impersonationService.validateImpersonationId( + aImpersonationId, + this.request.user.id + ); const portfolio = await this.createPortfolio( impersonationUserId || this.request.user.id @@ -127,10 +133,11 @@ export class PortfolioService { aImpersonationId: string, aDateRange: DateRange = 'max' ): Promise { - const impersonationUserId = await this.impersonationService.validateImpersonationId( - aImpersonationId, - this.request.user.id - ); + const impersonationUserId = + await this.impersonationService.validateImpersonationId( + aImpersonationId, + this.request.user.id + ); const portfolio = await this.createPortfolio( impersonationUserId || this.request.user.id @@ -148,6 +155,11 @@ export class PortfolioService { return portfolio .get() .filter((portfolioItem) => { + if (isAfter(parseISO(portfolioItem.date), endOfToday())) { + // Filter out future dates + return false; + } + if (dateRangeDate === undefined) { return true; } @@ -170,10 +182,11 @@ export class PortfolioService { public async getOverview( aImpersonationId: string ): Promise { - const impersonationUserId = await this.impersonationService.validateImpersonationId( - aImpersonationId, - this.request.user.id - ); + const impersonationUserId = + await this.impersonationService.validateImpersonationId( + aImpersonationId, + this.request.user.id + ); const portfolio = await this.createPortfolio( impersonationUserId || this.request.user.id @@ -195,10 +208,11 @@ export class PortfolioService { aImpersonationId: string, aSymbol: string ): Promise { - const impersonationUserId = await this.impersonationService.validateImpersonationId( - aImpersonationId, - this.request.user.id - ); + const impersonationUserId = + await this.impersonationService.validateImpersonationId( + aImpersonationId, + this.request.user.id + ); const portfolio = await this.createPortfolio( impersonationUserId || this.request.user.id @@ -318,7 +332,7 @@ export class PortfolioService { const historicalDataArray: HistoricalDataItem[] = []; - for (const [date, { marketPrice, performance }] of Object.entries( + for (const [date, { marketPrice }] of Object.entries( historicalData[aSymbol] ).reverse()) { historicalDataArray.push({ diff --git a/apps/api/src/models/order.ts b/apps/api/src/models/order.ts index 0a741ddc6..621e07de8 100644 --- a/apps/api/src/models/order.ts +++ b/apps/api/src/models/order.ts @@ -1,4 +1,5 @@ -import { Account, Currency, Platform, SymbolProfile } from '@prisma/client'; +import { Account, Currency, SymbolProfile } from '@prisma/client'; +import { endOfToday, isAfter, parseISO } from 'date-fns'; import { v4 as uuidv4 } from 'uuid'; import { IOrder } from '../services/interfaces/interfaces'; @@ -52,6 +53,10 @@ export class Order { return this.id; } + public getIsDraft() { + return isAfter(parseISO(this.date), endOfToday()); + } + public getQuantity() { return this.quantity; } diff --git a/apps/api/src/models/portfolio.spec.ts b/apps/api/src/models/portfolio.spec.ts index 5cd50d8ff..2f78df3fe 100644 --- a/apps/api/src/models/portfolio.spec.ts +++ b/apps/api/src/models/portfolio.spec.ts @@ -275,7 +275,9 @@ describe('Portfolio', () => { expect(portfolio.getPositions(getYesterday())).toMatchObject({}); - expect(portfolio.getSymbols(getYesterday())).toEqual(['BTCUSD']); + expect(portfolio.getSymbols(getYesterday())).toEqual([]); + + expect(portfolio.getSymbols(new Date())).toEqual(['BTCUSD']); }); }); @@ -309,16 +311,16 @@ describe('Portfolio', () => { ) ); - const details = await portfolio.getDetails('1d'); + /*const details = await portfolio.getDetails('1d'); expect(details).toMatchObject({ ETHUSD: { accounts: { [UNKNOWN_KEY]: { - /*current: exchangeRateDataService.toCurrency( + current: exchangeRateDataService.toCurrency( 0.2 * 991.49, Currency.USD, baseCurrency - ),*/ + ), original: exchangeRateDataService.toCurrency( 0.2 * 991.49, Currency.USD, @@ -345,7 +347,7 @@ describe('Portfolio', () => { symbol: 'ETHUSD', type: 'Cryptocurrency' } - }); + });*/ expect(portfolio.getFees()).toEqual(0); diff --git a/apps/api/src/models/portfolio.ts b/apps/api/src/models/portfolio.ts index d081fd2c5..4915f00b7 100644 --- a/apps/api/src/models/portfolio.ts +++ b/apps/api/src/models/portfolio.ts @@ -73,7 +73,7 @@ export class Portfolio implements PortfolioInterface { const [portfolioItemsYesterday] = this.get(yesterday); - let positions: { [symbol: string]: Position } = {}; + const positions: { [symbol: string]: Position } = {}; this.getSymbols().forEach((symbol) => { positions[symbol] = { @@ -105,14 +105,49 @@ export class Portfolio implements PortfolioInterface { ); // Set value after pushing today's portfolio items - this.portfolioItems[portfolioItemsLength - 1].value = this.getValue( - today - ); + this.portfolioItems[portfolioItemsLength - 1].value = + this.getValue(today); } return this; } + public async addFuturePortfolioItems() { + let investment = this.getInvestment(new Date()); + + this.getOrders() + .filter((order) => order.getIsDraft() === true) + .forEach((order) => { + const portfolioItem = this.portfolioItems.find((item) => { + return item.date === order.getDate(); + }); + + if (portfolioItem) { + portfolioItem.investment += this.exchangeRateDataService.toCurrency( + order.getTotal(), + order.getCurrency(), + this.user.Settings.currency + ); + } else { + investment += this.exchangeRateDataService.toCurrency( + order.getTotal(), + order.getCurrency(), + this.user.Settings.currency + ); + + this.portfolioItems.push({ + investment, + date: order.getDate(), + grossPerformancePercent: 0, + positions: {}, + value: 0 + }); + } + }); + + return this; + } + public createFromData({ orders, portfolioItems, @@ -178,6 +213,8 @@ export class Portfolio implements PortfolioInterface { if (filteredPortfolio) { return [cloneDeep(filteredPortfolio)]; } + + return []; } return cloneDeep(this.portfolioItems); @@ -239,12 +276,10 @@ export class Portfolio implements PortfolioInterface { if ( accounts[orderOfSymbol.getAccount()?.name || UNKNOWN_KEY]?.current ) { - accounts[ - orderOfSymbol.getAccount()?.name || UNKNOWN_KEY - ].current += currentValueOfSymbol; - accounts[ - orderOfSymbol.getAccount()?.name || UNKNOWN_KEY - ].original += originalValueOfSymbol; + accounts[orderOfSymbol.getAccount()?.name || UNKNOWN_KEY].current += + currentValueOfSymbol; + accounts[orderOfSymbol.getAccount()?.name || UNKNOWN_KEY].original += + originalValueOfSymbol; } else { accounts[orderOfSymbol.getAccount()?.name || UNKNOWN_KEY] = { current: currentValueOfSymbol, @@ -282,7 +317,7 @@ export class Portfolio implements PortfolioInterface { let now = portfolioItemsNow.positions[symbol].marketPrice; // 1d - let before = portfolioItemsBefore.positions[symbol].marketPrice; + let before = portfolioItemsBefore?.positions[symbol].marketPrice; if (aDateRange === 'ytd') { before = @@ -299,7 +334,7 @@ export class Portfolio implements PortfolioInterface { if ( !isBefore( parseISO(portfolioItemsNow.positions[symbol].firstBuyDate), - parseISO(portfolioItemsBefore.date) + parseISO(portfolioItemsBefore?.date) ) ) { // Trade was not before the date of portfolioItemsBefore, then override it with average price @@ -365,7 +400,11 @@ export class Portfolio implements PortfolioInterface { } public getMinDate() { - if (this.orders.length > 0) { + const orders = this.getOrders().filter( + (order) => order.getIsDraft() === false + ); + + if (orders.length > 0) { return new Date(this.orders[0].getDate()); } @@ -492,9 +531,11 @@ export class Portfolio implements PortfolioInterface { } } } else { - symbols = this.orders.map((order) => { - return order.getSymbol(); - }); + symbols = this.orders + .filter((order) => order.getIsDraft() === false) + .map((order) => { + return order.getSymbol(); + }); } // unique values @@ -503,7 +544,9 @@ export class Portfolio implements PortfolioInterface { public getTotalBuy() { return this.orders - .filter((order) => order.getType() === 'BUY') + .filter( + (order) => order.getIsDraft() === false && order.getType() === 'BUY' + ) .map((order) => { return this.exchangeRateDataService.toCurrency( order.getTotal(), @@ -516,7 +559,9 @@ export class Portfolio implements PortfolioInterface { public getTotalSell() { return this.orders - .filter((order) => order.getType() === 'SELL') + .filter( + (order) => order.getIsDraft() === false && order.getType() === 'SELL' + ) .map((order) => { return this.exchangeRateDataService.toCurrency( order.getTotal(), @@ -686,10 +731,10 @@ export class Portfolio implements PortfolioInterface { this.portfolioItems.push( cloneDeep({ + positions, date: yesterday.toISOString(), grossPerformancePercent: 0, investment: 0, - positions: positions, value: 0 }) ); @@ -746,8 +791,6 @@ export class Portfolio implements PortfolioInterface { } private updatePortfolioItems() { - // console.time('update-portfolio-items'); - let currentDate = new Date(); const year = getYear(currentDate); @@ -771,107 +814,99 @@ export class Portfolio implements PortfolioInterface { } this.orders.forEach((order) => { - let index = this.portfolioItems.findIndex((item) => { - const dateOfOrder = setDate(parseISO(order.getDate()), 1); - return isSameDay(parseISO(item.date), dateOfOrder); - }); - - if (index === -1) { - // if not found, we only have one order, which means we do not loop below - index = 0; - } - - for (let i = index; i < this.portfolioItems.length; i++) { - // Set currency - this.portfolioItems[i].positions[ - order.getSymbol() - ].currency = order.getCurrency(); + if (order.getIsDraft() === false) { + let index = this.portfolioItems.findIndex((item) => { + const dateOfOrder = setDate(parseISO(order.getDate()), 1); + return isSameDay(parseISO(item.date), dateOfOrder); + }); - this.portfolioItems[i].positions[ - order.getSymbol() - ].transactionCount += 1; + if (index === -1) { + // if not found, we only have one order, which means we do not loop below + index = 0; + } - if (order.getType() === 'BUY') { - if ( - !this.portfolioItems[i].positions[order.getSymbol()].firstBuyDate - ) { - this.portfolioItems[i].positions[ - order.getSymbol() - ].firstBuyDate = resetHours( - parseISO(order.getDate()) - ).toISOString(); - } + for (let i = index; i < this.portfolioItems.length; i++) { + // Set currency + this.portfolioItems[i].positions[order.getSymbol()].currency = + order.getCurrency(); this.portfolioItems[i].positions[ order.getSymbol() - ].quantity += order.getQuantity(); - this.portfolioItems[i].positions[ - order.getSymbol() - ].investment += this.exchangeRateDataService.toCurrency( - order.getTotal(), - order.getCurrency(), - this.user.Settings.currency - ); - this.portfolioItems[i].positions[ - order.getSymbol() - ].investmentInOriginalCurrency += order.getTotal(); - - this.portfolioItems[ - i - ].investment += this.exchangeRateDataService.toCurrency( - order.getTotal(), - order.getCurrency(), - this.user.Settings.currency - ); - } else if (order.getType() === 'SELL') { - this.portfolioItems[i].positions[ - order.getSymbol() - ].quantity -= order.getQuantity(); - - if ( - this.portfolioItems[i].positions[order.getSymbol()].quantity === 0 - ) { - this.portfolioItems[i].positions[order.getSymbol()].investment = 0; - this.portfolioItems[i].positions[ - order.getSymbol() - ].investmentInOriginalCurrency = 0; - } else { + ].transactionCount += 1; + + if (order.getType() === 'BUY') { + if ( + !this.portfolioItems[i].positions[order.getSymbol()].firstBuyDate + ) { + this.portfolioItems[i].positions[order.getSymbol()].firstBuyDate = + resetHours(parseISO(order.getDate())).toISOString(); + } + + this.portfolioItems[i].positions[order.getSymbol()].quantity += + order.getQuantity(); + this.portfolioItems[i].positions[order.getSymbol()].investment += + this.exchangeRateDataService.toCurrency( + order.getTotal(), + order.getCurrency(), + this.user.Settings.currency + ); this.portfolioItems[i].positions[ order.getSymbol() - ].investment -= this.exchangeRateDataService.toCurrency( - order.getTotal(), - order.getCurrency(), - this.user.Settings.currency - ); - this.portfolioItems[i].positions[ - order.getSymbol() - ].investmentInOriginalCurrency -= order.getTotal(); + ].investmentInOriginalCurrency += order.getTotal(); + + this.portfolioItems[i].investment += + this.exchangeRateDataService.toCurrency( + order.getTotal(), + order.getCurrency(), + this.user.Settings.currency + ); + } else if (order.getType() === 'SELL') { + this.portfolioItems[i].positions[order.getSymbol()].quantity -= + order.getQuantity(); + + if ( + this.portfolioItems[i].positions[order.getSymbol()].quantity === 0 + ) { + this.portfolioItems[i].positions[ + order.getSymbol() + ].investment = 0; + this.portfolioItems[i].positions[ + order.getSymbol() + ].investmentInOriginalCurrency = 0; + } else { + this.portfolioItems[i].positions[order.getSymbol()].investment -= + this.exchangeRateDataService.toCurrency( + order.getTotal(), + order.getCurrency(), + this.user.Settings.currency + ); + this.portfolioItems[i].positions[ + order.getSymbol() + ].investmentInOriginalCurrency -= order.getTotal(); + } + + this.portfolioItems[i].investment -= + this.exchangeRateDataService.toCurrency( + order.getTotal(), + order.getCurrency(), + this.user.Settings.currency + ); } - this.portfolioItems[ - i - ].investment -= this.exchangeRateDataService.toCurrency( - order.getTotal(), - order.getCurrency(), - this.user.Settings.currency - ); - } - - this.portfolioItems[i].positions[order.getSymbol()].averagePrice = - this.portfolioItems[i].positions[order.getSymbol()] - .investmentInOriginalCurrency / - this.portfolioItems[i].positions[order.getSymbol()].quantity; + this.portfolioItems[i].positions[order.getSymbol()].averagePrice = + this.portfolioItems[i].positions[order.getSymbol()] + .investmentInOriginalCurrency / + this.portfolioItems[i].positions[order.getSymbol()].quantity; - const currentValue = this.getValue( - parseISO(this.portfolioItems[i].date) - ); + const currentValue = this.getValue( + parseISO(this.portfolioItems[i].date) + ); - this.portfolioItems[i].grossPerformancePercent = - currentValue / this.portfolioItems[i].investment - 1 || 0; - this.portfolioItems[i].value = currentValue; + this.portfolioItems[i].grossPerformancePercent = + currentValue / this.portfolioItems[i].investment - 1 || 0; + this.portfolioItems[i].value = currentValue; + } } }); - - // console.timeEnd('update-portfolio-items'); } } diff --git a/apps/api/src/services/data-gathering.service.ts b/apps/api/src/services/data-gathering.service.ts index 96ba41735..5b1b35435 100644 --- a/apps/api/src/services/data-gathering.service.ts +++ b/apps/api/src/services/data-gathering.service.ts @@ -8,6 +8,7 @@ import { Injectable } from '@nestjs/common'; import { DataSource } from '@prisma/client'; import { differenceInHours, + endOfToday, format, getDate, getMonth, @@ -187,7 +188,8 @@ export class DataGatheringService { public async getCustomSymbolsToGather( startDate?: Date ): Promise { - const scraperConfigurations = await this.ghostfolioScraperApi.getScraperConfigurations(); + const scraperConfigurations = + await this.ghostfolioScraperApi.getScraperConfigurations(); return scraperConfigurations.map((scraperConfiguration) => { return { @@ -224,7 +226,12 @@ export class DataGatheringService { const distinctOrders = await this.prisma.order.findMany({ distinct: ['symbol'], orderBy: [{ symbol: 'asc' }], - select: { dataSource: true, symbol: true } + select: { dataSource: true, symbol: true }, + where: { + date: { + lt: endOfToday() // no draft + } + } }); const distinctOrdersWithDate: IDataGatheringItem[] = distinctOrders @@ -280,7 +287,12 @@ export class DataGatheringService { const distinctOrders = await this.prisma.order.findMany({ distinct: ['symbol'], orderBy: [{ date: 'asc' }], - select: { dataSource: true, date: true, symbol: true } + select: { dataSource: true, date: true, symbol: true }, + where: { + date: { + lt: endOfToday() // no draft + } + } }); return [ diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.html b/apps/client/src/app/components/investment-chart/investment-chart.component.html index 7878bbc8e..7598096ca 100644 --- a/apps/client/src/app/components/investment-chart/investment-chart.component.html +++ b/apps/client/src/app/components/investment-chart/investment-chart.component.html @@ -2,12 +2,12 @@ *ngIf="isLoading" animation="pulse" [theme]="{ - height: '12rem', + height: '100%', width: '100%' }" > diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.ts b/apps/client/src/app/components/investment-chart/investment-chart.component.ts index 38b787147..7dad55b7c 100644 --- a/apps/client/src/app/components/investment-chart/investment-chart.component.ts +++ b/apps/client/src/app/components/investment-chart/investment-chart.component.ts @@ -19,6 +19,7 @@ import { TimeScale } from 'chart.js'; import { Chart } from 'chart.js'; +import { addMonths, isAfter, parseISO, subMonths } from 'date-fns'; @Component({ selector: 'gf-investment-chart', @@ -52,9 +53,30 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy, OnInit { } } + public ngOnDestroy() { + this.chart?.destroy(); + } + private initialize() { this.isLoading = true; + if (this.portfolioItems?.length > 0) { + // Extend chart by three months (before) + const firstItem = this.portfolioItems[0]; + this.portfolioItems.unshift({ + ...firstItem, + date: subMonths(parseISO(firstItem.date), 3).toISOString(), + investment: 0 + }); + + // Extend chart by three months (after) + const lastItem = this.portfolioItems[this.portfolioItems.length - 1]; + this.portfolioItems.push({ + ...lastItem, + date: addMonths(parseISO(lastItem.date), 3).toISOString() + }); + } + const data = { labels: this.portfolioItems.map((position) => { return position.date; @@ -65,7 +87,16 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy, OnInit { borderWidth: 2, data: this.portfolioItems.map((position) => { return position.investment; - }) + }), + segment: { + borderColor: (context: unknown) => + this.isInFuture( + context, + `rgba(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b}, 0.67)` + ), + borderDash: (context: unknown) => this.isInFuture(context, [2, 2]) + }, + stepped: true } ] }; @@ -123,7 +154,9 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy, OnInit { } } - public ngOnDestroy() { - this.chart?.destroy(); + private isInFuture(aContext: any, aValue: any) { + return isAfter(new Date(aContext?.p0?.parsed?.x), new Date()) + ? aValue + : undefined; } } diff --git a/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.component.ts b/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.component.ts index 011627b90..126793b9b 100644 --- a/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.component.ts +++ b/apps/client/src/app/components/portfolio-proportion-chart/portfolio-proportion-chart.component.ts @@ -24,7 +24,8 @@ import { Chart } from 'chart.js'; styleUrls: ['./portfolio-proportion-chart.component.scss'] }) export class PortfolioProportionChartComponent - implements OnChanges, OnDestroy, OnInit { + implements OnChanges, OnDestroy, OnInit +{ @Input() baseCurrency: Currency; @Input() isInPercent: boolean; @Input() key: string; @@ -72,9 +73,8 @@ export class PortfolioProportionChartComponent Object.keys(this.positions).forEach((symbol) => { if (this.positions[symbol][this.key]) { if (chartData[this.positions[symbol][this.key]]) { - chartData[this.positions[symbol][this.key]].value += this.positions[ - symbol - ].value; + chartData[this.positions[symbol][this.key]].value += + this.positions[symbol].value; } else { chartData[this.positions[symbol][this.key]] = { value: this.positions[symbol].value @@ -114,7 +114,11 @@ export class PortfolioProportionChartComponent } rest.forEach((restItem) => { - unknownItem[1] = { value: unknownItem[1].value + restItem[1].value }; + if (unknownItem?.[1]) { + unknownItem[1] = { + value: unknownItem[1].value + restItem[1].value + }; + } }); // Sort data again diff --git a/apps/client/src/app/components/transactions-table/transactions-table.component.html b/apps/client/src/app/components/transactions-table/transactions-table.component.html index e8da18858..5cd389a3e 100644 --- a/apps/client/src/app/components/transactions-table/transactions-table.component.html +++ b/apps/client/src/app/components/transactions-table/transactions-table.component.html @@ -41,56 +41,50 @@ [dataSource]="dataSource" > - # + + # + {{ dataSource.data.length - i }} - + Date -
+
{{ element.date | date: defaultDateFormat }}
- + Type - +
- {{ element.type }} + {{ element.type }}
@@ -100,24 +94,30 @@ Symbol - {{ element.symbol | gfSymbol }} +
+ {{ element.symbol | gfSymbol }} + Draft +
Currency -
- {{ element.currency }} -
+ {{ element.currency }}
@@ -185,7 +185,9 @@ - Account + + Account +
; @ViewChild(MatSort) sort: MatSort; - public dataSource: MatTableDataSource = new MatTableDataSource(); + public dataSource: MatTableDataSource = + new MatTableDataSource(); public defaultDateFormat = DEFAULT_DATE_FORMAT; public displayedColumns = []; + public endOfToday = endOfToday(); public filters$: Subject = new BehaviorSubject([]); public filters: Observable = this.filters$.asObservable(); + public isAfter = isAfter; public isLoading = true; public placeholder = ''; public routeQueryParams: Subscription; diff --git a/apps/client/src/app/pages/tools/analysis/analysis-page.html b/apps/client/src/app/pages/tools/analysis/analysis-page.html index 9a4162597..4223ced07 100644 --- a/apps/client/src/app/pages/tools/analysis/analysis-page.html +++ b/apps/client/src/app/pages/tools/analysis/analysis-page.html @@ -192,7 +192,7 @@
-
+
diff --git a/apps/client/src/app/pages/tools/analysis/analysis-page.scss b/apps/client/src/app/pages/tools/analysis/analysis-page.scss index db8237522..cbd3fde58 100644 --- a/apps/client/src/app/pages/tools/analysis/analysis-page.scss +++ b/apps/client/src/app/pages/tools/analysis/analysis-page.scss @@ -1,4 +1,16 @@ :host { + .investment-chart { + .mat-card { + .mat-card-content { + aspect-ratio: 16 / 9; + + gf-investment-chart { + height: 100%; + } + } + } + } + .proportion-charts { .mat-card { .mat-card-content { diff --git a/apps/client/src/styles/bootstrap.scss b/apps/client/src/styles/bootstrap.scss index f2792d01a..3835c6b62 100644 --- a/apps/client/src/styles/bootstrap.scss +++ b/apps/client/src/styles/bootstrap.scss @@ -27,7 +27,7 @@ // @import '~bootstrap/scss/card'; // @import '~bootstrap/scss/breadcrumb'; // @import '~bootstrap/scss/pagination'; -// @import '~bootstrap/scss/badge'; +@import '~bootstrap/scss/badge'; // @import '~bootstrap/scss/jumbotron'; // @import '~bootstrap/scss/alert'; // @import '~bootstrap/scss/progress';