Drafts for orders (#187)

* Render the future with a dashed border

* Update changelog
pull/190/head
Thomas 3 years ago committed by GitHub
parent ce2d8d519d
commit 92d321a001
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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

@ -68,10 +68,11 @@ export class OrderController {
public async getAllOrders(
@Headers('impersonation-id') impersonationId
): Promise<OrderModel[]> {
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: {

@ -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<Order> {
this.redisCacheService.remove(`${aUserId}.portfolio`);
// Gather symbol data of order in the background
this.dataGatheringService.gatherSymbols([
{
dataSource: data.dataSource,
date: <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: <Date>data.date,
symbol: data.symbol
}
]);
}
await this.cacheService.flush(aUserId);

@ -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<Portfolio> {
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<PortfolioItem[]> {
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<HistoricalDataItem[]> {
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<PortfolioOverview> {
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<PortfolioPositionDetail> {
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({

@ -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;
}

@ -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);

@ -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');
}
}

@ -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<IDataGatheringItem[]> {
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 [

@ -2,12 +2,12 @@
*ngIf="isLoading"
animation="pulse"
[theme]="{
height: '12rem',
height: '100%',
width: '100%'
}"
></ngx-skeleton-loader>
<canvas
#chartCanvas
height="50"
class="h-100"
[ngStyle]="{ display: isLoading ? 'none' : 'block' }"
></canvas>

@ -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;
}
}

@ -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

@ -41,56 +41,50 @@
[dataSource]="dataSource"
>
<ng-container matColumnDef="count">
<th *matHeaderCellDef class="px-1 text-right" i18n mat-header-cell>#</th>
<th
*matHeaderCellDef
class="d-none d-lg-table-cell px-1 text-right"
i18n
mat-header-cell
>
#
</th>
<td
*matCellDef="let element; let i = index"
class="px-1 text-right"
class="d-none d-lg-table-cell px-1 text-right"
mat-cell
>
{{ dataSource.data.length - i }}
</td>
</ng-container>
<ng-container matColumnDef="date">
<th
*matHeaderCellDef
class="justify-content-center px-1"
i18n
mat-header-cell
mat-sort-header
>
<th *matHeaderCellDef class="px-1" i18n mat-header-cell mat-sort-header>
Date
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
<div class="d-flex justify-content-center">
<div class="d-flex">
{{ element.date | date: defaultDateFormat }}
</div>
</td>
</ng-container>
<ng-container matColumnDef="type">
<th
*matHeaderCellDef
class="justify-content-center px-1"
i18n
mat-header-cell
mat-sort-header
>
<th *matHeaderCellDef class="px-1" i18n mat-header-cell mat-sort-header>
Type
</th>
<td mat-cell *matCellDef="let element" class="px-1 text-center">
<td mat-cell *matCellDef="let element" class="px-1">
<div
class="d-inline-flex justify-content-center pl-1 pr-2 py-1 type-badge"
class="d-inline-flex p-1 type-badge"
[ngClass]="element.type == 'BUY' ? 'buy' : 'sell'"
>
<ion-icon
class="mr-1"
[name]="
element.type === 'BUY'
? 'arrow-forward-circle-outline'
: 'arrow-back-circle-outline'
"
></ion-icon>
<span>{{ element.type }}</span>
<span class="d-none d-lg-block mx-1">{{ element.type }}</span>
</div>
</td>
</ng-container>
@ -100,24 +94,30 @@
Symbol
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
{{ element.symbol | gfSymbol }}
<div class="d-flex align-items-center">
{{ element.symbol | gfSymbol }}
<span
*ngIf="isAfter(element.date, endOfToday)"
class="badge badge-secondary ml-1"
i18n
>Draft</span
>
</div>
</td>
</ng-container>
<ng-container matColumnDef="currency">
<th
*matHeaderCellDef
class="d-none d-lg-table-cell justify-content-center px-1"
mat-header-cell
class="d-none d-lg-table-cell px-1"
i18n
mat-header-cell
mat-sort-header
>
Currency
</th>
<td *matCellDef="let element" class="d-none d-lg-table-cell px-1" mat-cell>
<div class="d-flex justify-content-center">
{{ element.currency }}
</div>
{{ element.currency }}
</td>
</ng-container>
@ -185,7 +185,9 @@
</ng-container>
<ng-container matColumnDef="account">
<th *matHeaderCellDef class="px-1" i18n mat-header-cell>Account</th>
<th *matHeaderCellDef class="px-1" mat-header-cell>
<span class="d-none d-lg-block" i18n>Account</span>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
<div class="d-flex">
<gf-symbol-icon

@ -23,7 +23,7 @@ import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router';
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
import { OrderWithAccount } from '@ghostfolio/common/types';
import { format } from 'date-fns';
import { endOfToday, format, isAfter } from 'date-fns';
import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -39,7 +39,8 @@ const SEARCH_STRING_SEPARATOR = ',';
styleUrls: ['./transactions-table.component.scss']
})
export class TransactionsTableComponent
implements OnChanges, OnDestroy, OnInit {
implements OnChanges, OnDestroy, OnInit
{
@Input() baseCurrency: string;
@Input() deviceType: string;
@Input() locale: string;
@ -54,11 +55,14 @@ export class TransactionsTableComponent
@ViewChild('searchInput') searchInput: ElementRef<HTMLInputElement>;
@ViewChild(MatSort) sort: MatSort;
public dataSource: MatTableDataSource<OrderWithAccount> = new MatTableDataSource();
public dataSource: MatTableDataSource<OrderWithAccount> =
new MatTableDataSource();
public defaultDateFormat = DEFAULT_DATE_FORMAT;
public displayedColumns = [];
public endOfToday = endOfToday();
public filters$: Subject<string[]> = new BehaviorSubject([]);
public filters: Observable<string[]> = this.filters$.asObservable();
public isAfter = isAfter;
public isLoading = true;
public placeholder = '';
public routeQueryParams: Subscription;

@ -192,7 +192,7 @@
</mat-card>
</div>
</div>
<div class="row">
<div class="investment-chart row">
<div class="col-lg">
<mat-card class="mb-3">
<mat-card-header>

@ -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 {

@ -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';

Loading…
Cancel
Save