Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
d9ea255c17 | |||
2c19d8c8e7 | |||
db090229ce | |||
fbe590ddb9 | |||
0d65136a9e | |||
dea87cc3cf | |||
a062a3cee4 | |||
5b1b207a6f | |||
63cc7b2871 | |||
3986e8f879 |
15
CHANGELOG.md
15
CHANGELOG.md
@ -5,6 +5,21 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## 1.24.0 - 07.07.2021
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added the total value in the create or edit transaction dialog
|
||||||
|
- Added a balance attribute to the account model
|
||||||
|
- Calculated the total balance (cash)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Upgraded `@angular/cdk` and `@angular/material` from version `11.0.4` to `12.0.6`
|
||||||
|
- Upgraded `@nestjs` dependencies
|
||||||
|
- Upgraded `angular-material-css-vars` from version `1.2.0` to `2.0.0`
|
||||||
|
- Upgraded `Nx` from version `12.3.6` to `12.5.4`
|
||||||
|
|
||||||
## 1.23.1 - 03.07.2021
|
## 1.23.1 - 03.07.2021
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
**Ghostfolio** is an open source portfolio tracker based on web technology. The software empowers busy folks to have a sharp look of their financial assets and to make solid, data-driven investment decisions by evaluating automated static portfolio analysis rules.
|
**Ghostfolio** is an open source portfolio tracker built with web technology. The software empowers busy people to have a sharp look of their financial assets and to make solid, data-driven investment decisions.
|
||||||
|
|
||||||
## Why Ghostfolio?
|
## Why Ghostfolio?
|
||||||
|
|
||||||
@ -79,8 +79,8 @@ The frontend is built with [Angular](https://angular.io) and uses [Angular Mater
|
|||||||
1. Run `yarn setup:database` to initialize the database schema and populate your database with (example) data
|
1. Run `yarn setup:database` to initialize the database schema and populate your database with (example) data
|
||||||
1. Start server and client (see [_Development_](#Development))
|
1. Start server and client (see [_Development_](#Development))
|
||||||
1. Login as _Admin_ with the following _Security Token_: `ae76872ae8f3419c6d6f64bf51888ecbcc703927a342d815fafe486acdb938da07d0cf44fca211a0be74a423238f535362d390a41e81e633a9ce668a6e31cdf9`
|
1. Login as _Admin_ with the following _Security Token_: `ae76872ae8f3419c6d6f64bf51888ecbcc703927a342d815fafe486acdb938da07d0cf44fca211a0be74a423238f535362d390a41e81e633a9ce668a6e31cdf9`
|
||||||
1. Go to the _Admin Control Panel_ and press _Gather All Data_ to fetch historical data
|
1. Go to the _Admin Control Panel_ and click _Gather All Data_ to fetch historical data
|
||||||
1. Press _Sign out_ and check out the _Live Demo_
|
1. Click _Sign out_ and check out the _Live Demo_
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
@ -11,5 +11,6 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
moduleFileExtensions: ['ts', 'js', 'html'],
|
moduleFileExtensions: ['ts', 'js', 'html'],
|
||||||
coverageDirectory: '../../coverage/apps/api',
|
coverageDirectory: '../../coverage/apps/api',
|
||||||
testTimeout: 10000
|
testTimeout: 10000,
|
||||||
|
testEnvironment: 'node'
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,7 @@ import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alph
|
|||||||
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||||
import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||||
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
|
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||||
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
@ -20,6 +21,7 @@ import { AccountService } from './account.service';
|
|||||||
AlphaVantageService,
|
AlphaVantageService,
|
||||||
ConfigurationService,
|
ConfigurationService,
|
||||||
DataProviderService,
|
DataProviderService,
|
||||||
|
ExchangeRateDataService,
|
||||||
GhostfolioScraperApiService,
|
GhostfolioScraperApiService,
|
||||||
ImpersonationService,
|
ImpersonationService,
|
||||||
PrismaService,
|
PrismaService,
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Account, Order, Prisma } from '@prisma/client';
|
import { Account, Currency, Order, Prisma } from '@prisma/client';
|
||||||
|
|
||||||
import { RedisCacheService } from '../redis-cache/redis-cache.service';
|
import { RedisCacheService } from '../redis-cache/redis-cache.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccountService {
|
export class AccountService {
|
||||||
public constructor(
|
public constructor(
|
||||||
|
private exchangeRateDataService: ExchangeRateDataService,
|
||||||
private readonly redisCacheService: RedisCacheService,
|
private readonly redisCacheService: RedisCacheService,
|
||||||
private prisma: PrismaService
|
private prisma: PrismaService
|
||||||
) {}
|
) {}
|
||||||
@ -53,6 +55,24 @@ export class AccountService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async calculateCashBalance(aUserId: string, aCurrency: Currency) {
|
||||||
|
let totalCashBalance = 0;
|
||||||
|
|
||||||
|
const accounts = await this.accounts({
|
||||||
|
where: { userId: aUserId }
|
||||||
|
});
|
||||||
|
|
||||||
|
accounts.forEach((account) => {
|
||||||
|
totalCashBalance += this.exchangeRateDataService.toCurrency(
|
||||||
|
account.balance,
|
||||||
|
account.currency,
|
||||||
|
aCurrency
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return totalCashBalance;
|
||||||
|
}
|
||||||
|
|
||||||
public async createAccount(
|
public async createAccount(
|
||||||
data: Prisma.AccountCreateInput,
|
data: Prisma.AccountCreateInput,
|
||||||
aUserId: string
|
aUserId: string
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
import { AccountType } from '@prisma/client';
|
import { AccountType, Currency } from '@prisma/client';
|
||||||
import { IsString, ValidateIf } from 'class-validator';
|
import { IsNumber, IsString, ValidateIf } from 'class-validator';
|
||||||
|
|
||||||
export class CreateAccountDto {
|
export class CreateAccountDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
accountType: AccountType;
|
accountType: AccountType;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
balance: number;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
currency: Currency;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
import { AccountType } from '@prisma/client';
|
import { AccountType, Currency } from '@prisma/client';
|
||||||
import { IsString, ValidateIf } from 'class-validator';
|
import { IsNumber, IsString, ValidateIf } from 'class-validator';
|
||||||
|
|
||||||
export class UpdateAccountDto {
|
export class UpdateAccountDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
accountType: AccountType;
|
accountType: AccountType;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
balance: number;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
currency: Currency;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Currency, DataSource, Type } from '@prisma/client';
|
import { Currency, DataSource, Type } from '@prisma/client';
|
||||||
import { IsISO8601, IsNumber, IsString, ValidateIf } from 'class-validator';
|
import { IsISO8601, IsNumber, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class CreateOrderDto {
|
export class CreateOrderDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ -142,7 +142,8 @@ export class PortfolioController {
|
|||||||
): Promise<{ [symbol: string]: PortfolioPosition }> {
|
): Promise<{ [symbol: string]: PortfolioPosition }> {
|
||||||
let details: { [symbol: string]: PortfolioPosition } = {};
|
let details: { [symbol: string]: PortfolioPosition } = {};
|
||||||
|
|
||||||
const impersonationUserId = await this.impersonationService.validateImpersonationId(
|
const impersonationUserId =
|
||||||
|
await this.impersonationService.validateImpersonationId(
|
||||||
impersonationId,
|
impersonationId,
|
||||||
this.request.user.id
|
this.request.user.id
|
||||||
);
|
);
|
||||||
@ -221,6 +222,7 @@ export class PortfolioController {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
overview = nullifyValuesInObject(overview, [
|
overview = nullifyValuesInObject(overview, [
|
||||||
|
'cash',
|
||||||
'committedFunds',
|
'committedFunds',
|
||||||
'fees',
|
'fees',
|
||||||
'totalBuy',
|
'totalBuy',
|
||||||
@ -238,7 +240,8 @@ export class PortfolioController {
|
|||||||
@Query('range') range,
|
@Query('range') range,
|
||||||
@Res() res: Response
|
@Res() res: Response
|
||||||
): Promise<PortfolioPerformance> {
|
): Promise<PortfolioPerformance> {
|
||||||
const impersonationUserId = await this.impersonationService.validateImpersonationId(
|
const impersonationUserId =
|
||||||
|
await this.impersonationService.validateImpersonationId(
|
||||||
impersonationId,
|
impersonationId,
|
||||||
this.request.user.id
|
this.request.user.id
|
||||||
);
|
);
|
||||||
@ -306,7 +309,8 @@ export class PortfolioController {
|
|||||||
public async getReport(
|
public async getReport(
|
||||||
@Headers('impersonation-id') impersonationId
|
@Headers('impersonation-id') impersonationId
|
||||||
): Promise<PortfolioReport> {
|
): Promise<PortfolioReport> {
|
||||||
const impersonationUserId = await this.impersonationService.validateImpersonationId(
|
const impersonationUserId =
|
||||||
|
await this.impersonationService.validateImpersonationId(
|
||||||
impersonationId,
|
impersonationId,
|
||||||
this.request.user.id
|
this.request.user.id
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
||||||
|
import { CacheService } from '@ghostfolio/api/app/cache/cache.service';
|
||||||
|
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
||||||
|
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
||||||
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
|
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
|
||||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
||||||
@ -11,10 +16,6 @@ import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
|||||||
import { RulesService } from '@ghostfolio/api/services/rules.service';
|
import { RulesService } from '@ghostfolio/api/services/rules.service';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { CacheService } from '../cache/cache.service';
|
|
||||||
import { OrderService } from '../order/order.service';
|
|
||||||
import { RedisCacheModule } from '../redis-cache/redis-cache.module';
|
|
||||||
import { UserService } from '../user/user.service';
|
|
||||||
import { PortfolioController } from './portfolio.controller';
|
import { PortfolioController } from './portfolio.controller';
|
||||||
import { PortfolioService } from './portfolio.service';
|
import { PortfolioService } from './portfolio.service';
|
||||||
|
|
||||||
@ -22,6 +23,7 @@ import { PortfolioService } from './portfolio.service';
|
|||||||
imports: [RedisCacheModule],
|
imports: [RedisCacheModule],
|
||||||
controllers: [PortfolioController],
|
controllers: [PortfolioController],
|
||||||
providers: [
|
providers: [
|
||||||
|
AccountService,
|
||||||
AlphaVantageService,
|
AlphaVantageService,
|
||||||
CacheService,
|
CacheService,
|
||||||
ConfigurationService,
|
ConfigurationService,
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
||||||
|
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||||
import { Portfolio } from '@ghostfolio/api/models/portfolio';
|
import { Portfolio } from '@ghostfolio/api/models/portfolio';
|
||||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||||
@ -30,9 +34,6 @@ import {
|
|||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import * as roundTo from 'round-to';
|
import * as roundTo from 'round-to';
|
||||||
|
|
||||||
import { OrderService } from '../order/order.service';
|
|
||||||
import { RedisCacheService } from '../redis-cache/redis-cache.service';
|
|
||||||
import { UserService } from '../user/user.service';
|
|
||||||
import {
|
import {
|
||||||
HistoricalDataItem,
|
HistoricalDataItem,
|
||||||
PortfolioPositionDetail
|
PortfolioPositionDetail
|
||||||
@ -41,6 +42,7 @@ import {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class PortfolioService {
|
export class PortfolioService {
|
||||||
public constructor(
|
public constructor(
|
||||||
|
private readonly accountService: AccountService,
|
||||||
private readonly dataProviderService: DataProviderService,
|
private readonly dataProviderService: DataProviderService,
|
||||||
private readonly exchangeRateDataService: ExchangeRateDataService,
|
private readonly exchangeRateDataService: ExchangeRateDataService,
|
||||||
private readonly impersonationService: ImpersonationService,
|
private readonly impersonationService: ImpersonationService,
|
||||||
@ -192,10 +194,15 @@ export class PortfolioService {
|
|||||||
impersonationUserId || this.request.user.id
|
impersonationUserId || this.request.user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const cash = await this.accountService.calculateCashBalance(
|
||||||
|
impersonationUserId || this.request.user.id,
|
||||||
|
this.request.user.Settings.currency
|
||||||
|
);
|
||||||
const committedFunds = portfolio.getCommittedFunds();
|
const committedFunds = portfolio.getCommittedFunds();
|
||||||
const fees = portfolio.getFees();
|
const fees = portfolio.getFees();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
cash,
|
||||||
committedFunds,
|
committedFunds,
|
||||||
fees,
|
fees,
|
||||||
ordersCount: portfolio.getOrders().length,
|
ordersCount: portfolio.getOrders().length,
|
||||||
|
@ -110,7 +110,9 @@ describe('Portfolio', () => {
|
|||||||
Account: [
|
Account: [
|
||||||
{
|
{
|
||||||
accountType: AccountType.SECURITIES,
|
accountType: AccountType.SECURITIES,
|
||||||
|
balance: 0,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
currency: Currency.USD,
|
||||||
id: DEFAULT_ACCOUNT_ID,
|
id: DEFAULT_ACCOUNT_ID,
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
name: 'Default Account',
|
name: 'Default Account',
|
||||||
|
@ -5,13 +5,7 @@ module.exports = {
|
|||||||
globals: {
|
globals: {
|
||||||
'ts-jest': {
|
'ts-jest': {
|
||||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
stringifyContentPathRegex: '\\.(html|svg)$'
|
||||||
astTransformers: {
|
|
||||||
before: [
|
|
||||||
'jest-preset-angular/build/InlineFilesTransformer',
|
|
||||||
'jest-preset-angular/build/StripStylesTransformer'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
coverageDirectory: '../../coverage/apps/client',
|
coverageDirectory: '../../coverage/apps/client',
|
||||||
@ -19,5 +13,6 @@ module.exports = {
|
|||||||
'jest-preset-angular/build/serializers/no-ng-attributes',
|
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||||
'jest-preset-angular/build/serializers/ng-snapshot',
|
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||||
'jest-preset-angular/build/serializers/html-comment'
|
'jest-preset-angular/build/serializers/html-comment'
|
||||||
]
|
],
|
||||||
|
transform: { '^.+\\.(ts|js|html)$': 'jest-preset-angular' }
|
||||||
};
|
};
|
||||||
|
@ -7,10 +7,6 @@
|
|||||||
.create-account-box {
|
.create-account-box {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
|
|
||||||
.link {
|
|
||||||
color: rgba(var(--palette-primary-500), 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,27 @@
|
|||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="transactions">
|
||||||
|
<th *matHeaderCellDef class="text-right" i18n mat-header-cell>
|
||||||
|
Transactions
|
||||||
|
</th>
|
||||||
|
<td *matCellDef="let element" class="text-right" mat-cell>
|
||||||
|
{{ element.Order?.length }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="balance">
|
||||||
|
<th *matHeaderCellDef class="text-right" i18n mat-header-cell>Balance</th>
|
||||||
|
<td *matCellDef="let element" class="text-right" mat-cell>
|
||||||
|
<gf-value
|
||||||
|
class="d-inline-block justify-content-end"
|
||||||
|
[currency]="element.currency"
|
||||||
|
[locale]="locale"
|
||||||
|
[value]="element.balance"
|
||||||
|
></gf-value>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="actions">
|
<ng-container matColumnDef="actions">
|
||||||
<th *matHeaderCellDef class="px-1 text-center" i18n mat-header-cell></th>
|
<th *matHeaderCellDef class="px-1 text-center" i18n mat-header-cell></th>
|
||||||
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
|
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
|
||||||
@ -53,15 +74,6 @@
|
|||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="transactions">
|
|
||||||
<th *matHeaderCellDef class="text-right" i18n mat-header-cell>
|
|
||||||
Transactions
|
|
||||||
</th>
|
|
||||||
<td *matCellDef="let element" class="text-right" mat-cell>
|
|
||||||
{{ element.Order?.length }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
|
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
|
||||||
<tr *matRowDef="let row; columns: displayedColumns" mat-row></tr>
|
<tr *matRowDef="let row; columns: displayedColumns" mat-row></tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -28,7 +28,8 @@ export class AccountsTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
@Output() accountDeleted = new EventEmitter<string>();
|
@Output() accountDeleted = new EventEmitter<string>();
|
||||||
@Output() accountToUpdate = new EventEmitter<AccountModel>();
|
@Output() accountToUpdate = new EventEmitter<AccountModel>();
|
||||||
|
|
||||||
public dataSource: MatTableDataSource<AccountModel> = new MatTableDataSource();
|
public dataSource: MatTableDataSource<AccountModel> =
|
||||||
|
new MatTableDataSource();
|
||||||
public displayedColumns = [];
|
public displayedColumns = [];
|
||||||
public isLoading = true;
|
public isLoading = true;
|
||||||
public routeQueryParams: Subscription;
|
public routeQueryParams: Subscription;
|
||||||
@ -40,7 +41,7 @@ export class AccountsTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
public ngOnInit() {}
|
public ngOnInit() {}
|
||||||
|
|
||||||
public ngOnChanges() {
|
public ngOnChanges() {
|
||||||
this.displayedColumns = ['account', 'platform', 'transactions'];
|
this.displayedColumns = ['account', 'platform', 'transactions', 'balance'];
|
||||||
|
|
||||||
if (this.showActions) {
|
if (this.showActions) {
|
||||||
this.displayedColumns.push('actions');
|
this.displayedColumns.push('actions');
|
||||||
|
@ -5,10 +5,7 @@
|
|||||||
z-index: 999;
|
z-index: 999;
|
||||||
|
|
||||||
.mat-toolbar {
|
.mat-toolbar {
|
||||||
background-color: rgba(
|
background-color: rgba(var(--light-disabled-text));
|
||||||
var(--light-primary-text),
|
|
||||||
var(--palette-foreground-disabled-alpha)
|
|
||||||
);
|
|
||||||
|
|
||||||
.spacer {
|
.spacer {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
@ -28,11 +25,6 @@
|
|||||||
|
|
||||||
:host-context(.is-dark-theme) {
|
:host-context(.is-dark-theme) {
|
||||||
.mat-toolbar {
|
.mat-toolbar {
|
||||||
background-color: rgba(
|
background-color: rgba(39, 39, 39, $alpha-disabled-text);
|
||||||
39,
|
|
||||||
39,
|
|
||||||
39,
|
|
||||||
var(--palette-foreground-disabled-alpha)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,18 @@
|
|||||||
<div class="container p-0">
|
<div class="container p-0">
|
||||||
|
<div class="row px-3 py-1">
|
||||||
|
<div class="d-flex flex-grow-1" i18n>Cash</div>
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<gf-value
|
||||||
|
class="justify-content-end"
|
||||||
|
[currency]="baseCurrency"
|
||||||
|
[locale]="locale"
|
||||||
|
[value]="isLoading ? undefined : overview?.cash"
|
||||||
|
></gf-value>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col"><hr /></div>
|
||||||
|
</div>
|
||||||
<div class="row px-3 py-1">
|
<div class="row px-3 py-1">
|
||||||
<div class="d-flex flex-grow-1" i18n>Buy</div>
|
<div class="d-flex flex-grow-1" i18n>Buy</div>
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
||||||
import { getCssVariable, getTextColor } from '@ghostfolio/common/helper';
|
import { getTextColor } from '@ghostfolio/common/helper';
|
||||||
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
|
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
import { Tooltip } from 'chart.js';
|
import { Tooltip } from 'chart.js';
|
||||||
@ -43,9 +43,7 @@ export class PortfolioProportionChartComponent
|
|||||||
private colorMap: {
|
private colorMap: {
|
||||||
[symbol: string]: string;
|
[symbol: string]: string;
|
||||||
} = {
|
} = {
|
||||||
[UNKNOWN_KEY]: `rgba(${getTextColor()}, ${getCssVariable(
|
[UNKNOWN_KEY]: `rgba(${getTextColor()}, 0.12)`
|
||||||
'--palette-foreground-divider-alpha'
|
|
||||||
)})`
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
|
@import '~apps/client/src/styles/ghostfolio-style';
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
gf-position {
|
gf-position {
|
||||||
&:nth-child(even) {
|
&:nth-child(even) {
|
||||||
background-color: rgba(
|
background-color: rgba(0, 0, 0, $alpha-hover);
|
||||||
var(--dark-primary-text),
|
|
||||||
var(--palette-background-hover-alpha)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -14,10 +13,7 @@
|
|||||||
:host-context(.is-dark-theme) {
|
:host-context(.is-dark-theme) {
|
||||||
gf-position {
|
gf-position {
|
||||||
&:nth-child(even) {
|
&:nth-child(even) {
|
||||||
background-color: rgba(
|
background-color: rgba(255, 255, 255, $alpha-hover);
|
||||||
var(--light-primary-text),
|
|
||||||
var(--palette-background-hover-alpha)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,7 @@
|
|||||||
padding: 0.15rem 0.75rem;
|
padding: 0.15rem 0.75rem;
|
||||||
|
|
||||||
&.mat-radio-checked {
|
&.mat-radio-checked {
|
||||||
background-color: rgba(
|
background-color: rgba(var(--dark-dividers));
|
||||||
var(--dark-primary-text),
|
|
||||||
var(--palette-foreground-divider-alpha)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep {
|
::ng-deep {
|
||||||
@ -33,15 +30,8 @@
|
|||||||
:host-context(.is-dark-theme) {
|
:host-context(.is-dark-theme) {
|
||||||
.mat-radio-button {
|
.mat-radio-button {
|
||||||
&.mat-radio-checked {
|
&.mat-radio-checked {
|
||||||
background-color: rgba(
|
background-color: rgba(var(--light-dividers));
|
||||||
var(--light-primary-text),
|
border: 1px solid rgba(var(--light-disabled-text));
|
||||||
var(--palette-foreground-divider-alpha)
|
|
||||||
);
|
|
||||||
border: 1px solid
|
|
||||||
rgba(
|
|
||||||
var(--light-primary-text),
|
|
||||||
var(--palette-foreground-disabled-button-alpha)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
::ng-deep {
|
::ng-deep {
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service';
|
import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service';
|
||||||
import { baseCurrency, DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
|
import { DEFAULT_DATE_FORMAT, baseCurrency } from '@ghostfolio/common/config';
|
||||||
import { Access, User } from '@ghostfolio/common/interfaces';
|
import { Access, User } from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
|
@ -125,6 +125,8 @@ export class AccountsPageComponent implements OnInit {
|
|||||||
|
|
||||||
public openUpdateAccountDialog({
|
public openUpdateAccountDialog({
|
||||||
accountType,
|
accountType,
|
||||||
|
balance,
|
||||||
|
currency,
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
platformId
|
platformId
|
||||||
@ -133,6 +135,8 @@ export class AccountsPageComponent implements OnInit {
|
|||||||
data: {
|
data: {
|
||||||
account: {
|
account: {
|
||||||
accountType,
|
accountType,
|
||||||
|
balance,
|
||||||
|
currency,
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
platformId
|
platformId
|
||||||
@ -167,6 +171,8 @@ export class AccountsPageComponent implements OnInit {
|
|||||||
data: {
|
data: {
|
||||||
account: {
|
account: {
|
||||||
accountType: AccountType.SECURITIES,
|
accountType: AccountType.SECURITIES,
|
||||||
|
balance: 0,
|
||||||
|
currency: this.user?.settings?.baseCurrency,
|
||||||
name: null,
|
name: null,
|
||||||
platformId: null
|
platformId: null
|
||||||
}
|
}
|
||||||
|
@ -8,14 +8,37 @@
|
|||||||
<input matInput name="name" required [(ngModel)]="data.account.name" />
|
<input matInput name="name" required [(ngModel)]="data.account.name" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-none">
|
<div>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Type</mat-label>
|
<mat-label i18n>Type</mat-label>
|
||||||
<mat-select name="type" required [(value)]="data.account.accountType">
|
<mat-select name="type" required [(value)]="data.account.accountType">
|
||||||
<mat-option value="SECURITIES" i18n> SECURITIES </mat-option>
|
<mat-option value="CASH" i18n>Cash</mat-option>
|
||||||
|
<mat-option value="SECURITIES" i18n>Securities</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
|
<mat-label i18n>Currency</mat-label>
|
||||||
|
<mat-select name="currency" required [(value)]="data.account.currency">
|
||||||
|
<mat-option *ngFor="let currency of currencies" [value]="currency"
|
||||||
|
>{{ currency }}</mat-option
|
||||||
|
>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
|
<mat-label i18n>Balance</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
name="balance"
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
[(ngModel)]="data.account.balance"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Platform</mat-label>
|
<mat-label i18n>Platform</mat-label>
|
||||||
|
@ -23,7 +23,7 @@ import { CreateOrUpdateTransactionDialogParams } from './interfaces/interfaces';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
host: { class: 'h-100' },
|
host: { class: 'h-100' },
|
||||||
selector: 'create-or-update-transaction-dialog',
|
selector: 'gf-create-or-update-transaction-dialog',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
styleUrls: ['./create-or-update-transaction-dialog.scss'],
|
styleUrls: ['./create-or-update-transaction-dialog.scss'],
|
||||||
templateUrl: 'create-or-update-transaction-dialog.html'
|
templateUrl: 'create-or-update-transaction-dialog.html'
|
||||||
|
@ -2,6 +2,22 @@
|
|||||||
<h1 *ngIf="data.transaction.id" mat-dialog-title i18n>Update transaction</h1>
|
<h1 *ngIf="data.transaction.id" mat-dialog-title i18n>Update transaction</h1>
|
||||||
<h1 *ngIf="!data.transaction.id" mat-dialog-title i18n>Add transaction</h1>
|
<h1 *ngIf="!data.transaction.id" mat-dialog-title i18n>Add transaction</h1>
|
||||||
<div class="flex-grow-1" mat-dialog-content>
|
<div class="flex-grow-1" mat-dialog-content>
|
||||||
|
<div>
|
||||||
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
|
<mat-label i18n>Account</mat-label>
|
||||||
|
<mat-select
|
||||||
|
name="accountId"
|
||||||
|
required
|
||||||
|
[(value)]="data.transaction.accountId"
|
||||||
|
>
|
||||||
|
<mat-option
|
||||||
|
*ngFor="let account of data.user?.accounts"
|
||||||
|
[value]="account.id"
|
||||||
|
>{{ account.name }}</mat-option
|
||||||
|
>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Symbol or ISIN</mat-label>
|
<mat-label i18n>Symbol or ISIN</mat-label>
|
||||||
@ -42,7 +58,7 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="d-none">
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Currency</mat-label>
|
<mat-label i18n>Currency</mat-label>
|
||||||
<mat-select
|
<mat-select
|
||||||
@ -136,22 +152,15 @@
|
|||||||
</button>
|
</button>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex" mat-dialog-actions>
|
||||||
|
<gf-value
|
||||||
|
class="flex-grow-1"
|
||||||
|
[currency]="data.transaction.currency"
|
||||||
|
[locale]="data.user?.settings?.locale"
|
||||||
|
[value]="data.transaction.fee + (data.transaction.quantity * data.transaction.unitPrice)"
|
||||||
|
></gf-value>
|
||||||
<div>
|
<div>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
|
||||||
<mat-label i18n>Account</mat-label>
|
|
||||||
<mat-select
|
|
||||||
name="accountId"
|
|
||||||
required
|
|
||||||
[(value)]="data.transaction.accountId"
|
|
||||||
>
|
|
||||||
<mat-option *ngFor="let account of data.accounts" [value]="account.id"
|
|
||||||
>{{ account.name }}</mat-option
|
|
||||||
>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="justify-content-end" mat-dialog-actions>
|
|
||||||
<button i18n mat-button (click)="onCancel()">Cancel</button>
|
<button i18n mat-button (click)="onCancel()">Cancel</button>
|
||||||
<button
|
<button
|
||||||
color="primary"
|
color="primary"
|
||||||
@ -163,4 +172,5 @@
|
|||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -9,6 +9,7 @@ import { MatFormFieldModule } from '@angular/material/form-field';
|
|||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { GfValueModule } from '@ghostfolio/client/components/value/value.module';
|
||||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||||
|
|
||||||
import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction-dialog.component';
|
import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction-dialog.component';
|
||||||
@ -19,6 +20,7 @@ import { CreateOrUpdateTransactionDialog } from './create-or-update-transaction-
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfSymbolModule,
|
GfSymbolModule,
|
||||||
|
GfValueModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
MatAutocompleteModule,
|
MatAutocompleteModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
|
.mat-dialog-actions {
|
||||||
|
gf-value {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mat-dialog-content {
|
.mat-dialog-content {
|
||||||
max-height: unset;
|
max-height: unset;
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
import { Account, Order } from '@prisma/client';
|
import { Account, Order } from '@prisma/client';
|
||||||
|
|
||||||
export interface CreateOrUpdateTransactionDialogParams {
|
export interface CreateOrUpdateTransactionDialogParams {
|
||||||
accountId: string;
|
accountId: string;
|
||||||
accounts: Account[];
|
|
||||||
transaction: Order;
|
transaction: Order;
|
||||||
|
user: User;
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,6 @@ export class TransactionsPageComponent implements OnInit {
|
|||||||
}: OrderModel): void {
|
}: OrderModel): void {
|
||||||
const dialogRef = this.dialog.open(CreateOrUpdateTransactionDialog, {
|
const dialogRef = this.dialog.open(CreateOrUpdateTransactionDialog, {
|
||||||
data: {
|
data: {
|
||||||
accounts: this.user.accounts,
|
|
||||||
transaction: {
|
transaction: {
|
||||||
accountId,
|
accountId,
|
||||||
currency,
|
currency,
|
||||||
@ -153,7 +152,8 @@ export class TransactionsPageComponent implements OnInit {
|
|||||||
symbol,
|
symbol,
|
||||||
type,
|
type,
|
||||||
unitPrice
|
unitPrice
|
||||||
}
|
},
|
||||||
|
user: this.user
|
||||||
},
|
},
|
||||||
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
|
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
|
||||||
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
||||||
@ -182,7 +182,6 @@ export class TransactionsPageComponent implements OnInit {
|
|||||||
private openCreateTransactionDialog(aTransaction?: OrderModel): void {
|
private openCreateTransactionDialog(aTransaction?: OrderModel): void {
|
||||||
const dialogRef = this.dialog.open(CreateOrUpdateTransactionDialog, {
|
const dialogRef = this.dialog.open(CreateOrUpdateTransactionDialog, {
|
||||||
data: {
|
data: {
|
||||||
accounts: this.user?.accounts,
|
|
||||||
transaction: {
|
transaction: {
|
||||||
accountId:
|
accountId:
|
||||||
aTransaction?.accountId ??
|
aTransaction?.accountId ??
|
||||||
@ -197,7 +196,8 @@ export class TransactionsPageComponent implements OnInit {
|
|||||||
symbol: aTransaction?.symbol ?? null,
|
symbol: aTransaction?.symbol ?? null,
|
||||||
type: aTransaction?.type ?? 'BUY',
|
type: aTransaction?.type ?? 'BUY',
|
||||||
unitPrice: null
|
unitPrice: null
|
||||||
}
|
},
|
||||||
|
user: this.user
|
||||||
},
|
},
|
||||||
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
|
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
|
||||||
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
||||||
|
@ -43,10 +43,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
border-top-color: rgba(
|
border-top-color: rgba(var(--light-dividers));
|
||||||
var(--light-primary-text),
|
|
||||||
var(--palette-foreground-divider-alpha)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngx-skeleton-loader {
|
ngx-skeleton-loader {
|
||||||
@ -63,10 +60,7 @@ body {
|
|||||||
background: var(--dark-background);
|
background: var(--dark-background);
|
||||||
|
|
||||||
&:not([class*='mat-elevation-z']) {
|
&:not([class*='mat-elevation-z']) {
|
||||||
border-color: rgba(
|
border-color: rgba(var(--light-dividers));
|
||||||
var(--light-primary-text),
|
|
||||||
var(--palette-foreground-divider-alpha)
|
|
||||||
);
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,8 +96,7 @@ button:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
border-top: 1px solid
|
border-top: 1px solid rgba(var(--dark-dividers));
|
||||||
rgba(var(--dark-primary-text), var(--palette-foreground-divider-alpha));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-icon {
|
ion-icon {
|
||||||
@ -138,8 +131,7 @@ ngx-skeleton-loader {
|
|||||||
|
|
||||||
.mat-card {
|
.mat-card {
|
||||||
&:not([class*='mat-elevation-z']) {
|
&:not([class*='mat-elevation-z']) {
|
||||||
border: 1px solid
|
border: 1px solid rgba(var(--dark-dividers));
|
||||||
rgba(var(--dark-primary-text), var(--palette-foreground-divider-alpha));
|
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,9 @@ $mat-css-dark-theme-selector: '.is-dark-theme';
|
|||||||
|
|
||||||
@import '~angular-material-css-vars/public-util';
|
@import '~angular-material-css-vars/public-util';
|
||||||
|
|
||||||
|
$alpha-disabled-text: 0.38;
|
||||||
|
$alpha-hover: 0.04;
|
||||||
|
|
||||||
.gf-table {
|
.gf-table {
|
||||||
td {
|
td {
|
||||||
border: 0;
|
border: 0;
|
||||||
|
@ -26,11 +26,15 @@ export function getCssVariable(aCssVariable: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getTextColor() {
|
export function getTextColor() {
|
||||||
return getCssVariable(
|
const cssVariable = getCssVariable(
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
? '--light-primary-text'
|
? '--light-primary-text'
|
||||||
: '--dark-primary-text'
|
: '--dark-primary-text'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [r, g, b] = cssVariable.split(',');
|
||||||
|
|
||||||
|
return `${r}, ${g}, ${b}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getToday() {
|
export function getToday() {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export interface PortfolioOverview {
|
export interface PortfolioOverview {
|
||||||
|
cash: number;
|
||||||
committedFunds: number;
|
committedFunds: number;
|
||||||
fees: number;
|
fees: number;
|
||||||
ordersCount: number;
|
ordersCount: number;
|
||||||
|
8
nx.json
8
nx.json
@ -35,5 +35,13 @@
|
|||||||
"common": {
|
"common": {
|
||||||
"tags": []
|
"tags": []
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"targetDependencies": {
|
||||||
|
"build": [
|
||||||
|
{
|
||||||
|
"target": "build",
|
||||||
|
"projects": "dependencies"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
54
package.json
54
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ghostfolio",
|
"name": "ghostfolio",
|
||||||
"version": "1.23.1",
|
"version": "1.24.0",
|
||||||
"homepage": "https://ghostfol.io",
|
"homepage": "https://ghostfol.io",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -46,32 +46,32 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "12.0.4",
|
"@angular/animations": "12.0.4",
|
||||||
"@angular/cdk": "11.0.4",
|
"@angular/cdk": "12.0.6",
|
||||||
"@angular/common": "12.0.4",
|
"@angular/common": "12.0.4",
|
||||||
"@angular/compiler": "12.0.4",
|
"@angular/compiler": "12.0.4",
|
||||||
"@angular/core": "12.0.4",
|
"@angular/core": "12.0.4",
|
||||||
"@angular/forms": "12.0.4",
|
"@angular/forms": "12.0.4",
|
||||||
"@angular/material": "11.0.4",
|
"@angular/material": "12.0.6",
|
||||||
"@angular/platform-browser": "12.0.4",
|
"@angular/platform-browser": "12.0.4",
|
||||||
"@angular/platform-browser-dynamic": "12.0.4",
|
"@angular/platform-browser-dynamic": "12.0.4",
|
||||||
"@angular/router": "12.0.4",
|
"@angular/router": "12.0.4",
|
||||||
"@codewithdan/observable-store": "2.2.11",
|
"@codewithdan/observable-store": "2.2.11",
|
||||||
"@nestjs/common": "7.6.5",
|
"@nestjs/common": "7.6.18",
|
||||||
"@nestjs/config": "0.6.1",
|
"@nestjs/config": "0.6.3",
|
||||||
"@nestjs/core": "7.6.5",
|
"@nestjs/core": "7.6.18",
|
||||||
"@nestjs/jwt": "7.2.0",
|
"@nestjs/jwt": "7.2.0",
|
||||||
"@nestjs/passport": "7.1.5",
|
"@nestjs/passport": "7.1.6",
|
||||||
"@nestjs/platform-express": "7.6.5",
|
"@nestjs/platform-express": "7.6.18",
|
||||||
"@nestjs/schedule": "0.4.1",
|
"@nestjs/schedule": "0.4.3",
|
||||||
"@nestjs/serve-static": "2.1.4",
|
"@nestjs/serve-static": "2.1.4",
|
||||||
"@nrwl/angular": "12.3.6",
|
"@nrwl/angular": "12.5.4",
|
||||||
"@prisma/client": "2.24.1",
|
"@prisma/client": "2.24.1",
|
||||||
"@simplewebauthn/browser": "3.0.0",
|
"@simplewebauthn/browser": "3.0.0",
|
||||||
"@simplewebauthn/server": "3.0.0",
|
"@simplewebauthn/server": "3.0.0",
|
||||||
"@simplewebauthn/typescript-types": "3.0.0",
|
"@simplewebauthn/typescript-types": "3.0.0",
|
||||||
"@stripe/stripe-js": "1.15.0",
|
"@stripe/stripe-js": "1.15.0",
|
||||||
"alphavantage": "2.2.0",
|
"alphavantage": "2.2.0",
|
||||||
"angular-material-css-vars": "1.2.0",
|
"angular-material-css-vars": "2.0.0",
|
||||||
"bent": "7.3.12",
|
"bent": "7.3.12",
|
||||||
"bootstrap": "4.6.0",
|
"bootstrap": "4.6.0",
|
||||||
"cache-manager": "3.4.3",
|
"cache-manager": "3.4.3",
|
||||||
@ -104,7 +104,7 @@
|
|||||||
"svgmap": "2.1.1",
|
"svgmap": "2.1.1",
|
||||||
"uuid": "8.3.2",
|
"uuid": "8.3.2",
|
||||||
"yahoo-finance": "0.3.6",
|
"yahoo-finance": "0.3.6",
|
||||||
"zone.js": "~0.11.4"
|
"zone.js": "0.11.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "12.0.4",
|
"@angular-devkit/build-angular": "12.0.4",
|
||||||
@ -112,17 +112,17 @@
|
|||||||
"@angular/cli": "12.0.4",
|
"@angular/cli": "12.0.4",
|
||||||
"@angular/compiler-cli": "12.0.4",
|
"@angular/compiler-cli": "12.0.4",
|
||||||
"@angular/language-service": "12.0.4",
|
"@angular/language-service": "12.0.4",
|
||||||
"@angular/localize": "11.0.9",
|
"@angular/localize": "12.0.5",
|
||||||
"@nestjs/schematics": "7.2.6",
|
"@nestjs/schematics": "7.3.1",
|
||||||
"@nestjs/testing": "7.6.5",
|
"@nestjs/testing": "7.6.18",
|
||||||
"@nrwl/cli": "12.3.6",
|
"@nrwl/cli": "12.5.4",
|
||||||
"@nrwl/cypress": "12.3.6",
|
"@nrwl/cypress": "12.5.4",
|
||||||
"@nrwl/eslint-plugin-nx": "12.3.6",
|
"@nrwl/eslint-plugin-nx": "12.5.4",
|
||||||
"@nrwl/jest": "12.3.6",
|
"@nrwl/jest": "12.5.4",
|
||||||
"@nrwl/nest": "12.3.6",
|
"@nrwl/nest": "12.5.4",
|
||||||
"@nrwl/node": "12.3.6",
|
"@nrwl/node": "12.5.4",
|
||||||
"@nrwl/tao": "12.3.6",
|
"@nrwl/tao": "12.5.4",
|
||||||
"@nrwl/workspace": "12.3.6",
|
"@nrwl/workspace": "12.5.4",
|
||||||
"@types/cache-manager": "3.4.0",
|
"@types/cache-manager": "3.4.0",
|
||||||
"@types/jest": "26.0.20",
|
"@types/jest": "26.0.20",
|
||||||
"@types/lodash": "4.14.168",
|
"@types/lodash": "4.14.168",
|
||||||
@ -139,12 +139,12 @@
|
|||||||
"import-sort-cli": "6.0.0",
|
"import-sort-cli": "6.0.0",
|
||||||
"import-sort-parser-typescript": "6.0.0",
|
"import-sort-parser-typescript": "6.0.0",
|
||||||
"import-sort-style-module": "6.0.0",
|
"import-sort-style-module": "6.0.0",
|
||||||
"jest": "26.6.3",
|
"jest": "27.0.3",
|
||||||
"jest-preset-angular": "8.4.0",
|
"jest-preset-angular": "9.0.3",
|
||||||
"prettier": "2.3.1",
|
"prettier": "2.3.2",
|
||||||
"replace-in-file": "6.2.0",
|
"replace-in-file": "6.2.0",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"ts-jest": "26.5.5",
|
"ts-jest": "27.0.3",
|
||||||
"ts-node": "9.1.1",
|
"ts-node": "9.1.1",
|
||||||
"typescript": "4.2.4"
|
"typescript": "4.2.4"
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
-- AlterEnum
|
||||||
|
ALTER TYPE "AccountType" ADD VALUE 'CASH';
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Account" ADD COLUMN "balance" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
ADD COLUMN "currency" "Currency" NOT NULL DEFAULT E'USD';
|
@ -26,7 +26,9 @@ model Access {
|
|||||||
|
|
||||||
model Account {
|
model Account {
|
||||||
accountType AccountType @default(SECURITIES)
|
accountType AccountType @default(SECURITIES)
|
||||||
|
balance Float @default(0)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
currency Currency @default(USD)
|
||||||
id String @default(uuid())
|
id String @default(uuid())
|
||||||
isDefault Boolean @default(false)
|
isDefault Boolean @default(false)
|
||||||
name String?
|
name String?
|
||||||
@ -158,6 +160,7 @@ model User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum AccountType {
|
enum AccountType {
|
||||||
|
CASH
|
||||||
SECURITIES
|
SECURITIES
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +87,8 @@ async function main() {
|
|||||||
create: [
|
create: [
|
||||||
{
|
{
|
||||||
accountType: AccountType.SECURITIES,
|
accountType: AccountType.SECURITIES,
|
||||||
|
balance: 0,
|
||||||
|
currency: Currency.USD,
|
||||||
id: 'f4425b66-9ba9-4ac4-93d7-fdf9a145e8cb',
|
id: 'f4425b66-9ba9-4ac4-93d7-fdf9a145e8cb',
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
name: 'Default Account'
|
name: 'Default Account'
|
||||||
@ -109,18 +111,24 @@ async function main() {
|
|||||||
create: [
|
create: [
|
||||||
{
|
{
|
||||||
accountType: AccountType.SECURITIES,
|
accountType: AccountType.SECURITIES,
|
||||||
|
balance: 0,
|
||||||
|
currency: Currency.USD,
|
||||||
id: 'd804de69-0429-42dc-b6ca-b308fd7dd926',
|
id: 'd804de69-0429-42dc-b6ca-b308fd7dd926',
|
||||||
name: 'Coinbase Account',
|
name: 'Coinbase Account',
|
||||||
platformId: platformCoinbase.id
|
platformId: platformCoinbase.id
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accountType: AccountType.SECURITIES,
|
accountType: AccountType.SECURITIES,
|
||||||
|
balance: 0,
|
||||||
|
currency: Currency.EUR,
|
||||||
id: '65cfb79d-b6c7-4591-9d46-73426bc62094',
|
id: '65cfb79d-b6c7-4591-9d46-73426bc62094',
|
||||||
name: 'DEGIRO Account',
|
name: 'DEGIRO Account',
|
||||||
platformId: platformDegiro.id
|
platformId: platformDegiro.id
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accountType: AccountType.SECURITIES,
|
accountType: AccountType.SECURITIES,
|
||||||
|
balance: 0,
|
||||||
|
currency: Currency.USD,
|
||||||
id: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
|
id: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
name: 'Interactive Brokers Account',
|
name: 'Interactive Brokers Account',
|
||||||
|
Reference in New Issue
Block a user