Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
5b20ba3382 | |||
15cc294581 | |||
b060b81204 | |||
a8d557eb1b | |||
6ae3a47b54 | |||
88c19eb45e | |||
7728706bc8 | |||
2e9d40c201 | |||
c002e37285 | |||
6be38a1c19 | |||
a3178fb213 | |||
e7158f6e16 | |||
dbea0456bc | |||
fefee11301 | |||
40836b745b | |||
07eabac059 | |||
48b412cfb8 | |||
b62488628c | |||
982c71c728 | |||
5aa16a3779 | |||
93de25e5b6 | |||
9acdb41aa2 |
@ -12,6 +12,12 @@
|
|||||||
"importOrder": ["^@ghostfolio/(.*)$", "<THIRD_PARTY_MODULES>", "^[./]"],
|
"importOrder": ["^@ghostfolio/(.*)$", "<THIRD_PARTY_MODULES>", "^[./]"],
|
||||||
"importOrderSeparation": true,
|
"importOrderSeparation": true,
|
||||||
"overrides": [
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.html",
|
||||||
|
"options": {
|
||||||
|
"parser": "angular"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"files": "*.ts",
|
"files": "*.ts",
|
||||||
"options": {
|
"options": {
|
||||||
|
55
CHANGELOG.md
55
CHANGELOG.md
@ -5,6 +5,61 @@ 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).
|
||||||
|
|
||||||
|
## 2.57.0 - 2024-02-25
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Moved the break down of the performance into asset and currency on the analysis page from experimental to general availability
|
||||||
|
- Restructured the `copy-assets` `Nx` target
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Changed the performances of the _Top 3_ and _Bottom 3_ performers on the analysis page to take the currency effects into account
|
||||||
|
|
||||||
|
## 2.56.0 - 2024-02-24
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Switched the performance calculations to take the currency effects into account
|
||||||
|
- Removed the `isDefault` flag from the `Account` database schema
|
||||||
|
- Exposed the database index of _Redis_ as an environment variable (`REDIS_DB`)
|
||||||
|
- Improved the language localization for German (`de`)
|
||||||
|
- Upgraded `prisma` from version `5.9.1` to `5.10.2`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Added the missing default currency to the prepare currencies function in the exchange rate data service
|
||||||
|
|
||||||
|
## 2.55.0 - 2024-02-22
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added indexes for `alias`, `granteeUserId` and `userId` to the access database table
|
||||||
|
- Added indexes for `currency`, `name` and `userId` to the account database table
|
||||||
|
- Added indexes for `accountId`, `date` and `updatedAt` to the account balance database table
|
||||||
|
- Added an index for `userId` to the auth device database table
|
||||||
|
- Added indexes for `marketPrice` and `state` to the market data database table
|
||||||
|
- Added indexes for `date`, `isDraft` and `userId` to the order database table
|
||||||
|
- Added an index for `name` to the platform database table
|
||||||
|
- Added indexes for `assetClass`, `currency`, `dataSource`, `isin`, `name` and `symbol` to the symbol profile database table
|
||||||
|
- Added an index for `userId` to the subscription database table
|
||||||
|
- Added an index for `name` to the tag database table
|
||||||
|
- Added indexes for `accessToken`, `createdAt`, `provider`, `role` and `thirdPartyId` to the user database table
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improved the validation for `currency` in various endpoints
|
||||||
|
- Harmonized the setting of a default locale in various components
|
||||||
|
- Set the parser to `angular` in the `prettier` options
|
||||||
|
|
||||||
|
## 2.54.0 - 2024-02-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added an index for `id` to the account database table
|
||||||
|
- Added indexes for `dataSource` and `date` to the market data database table
|
||||||
|
- Added an index for `accountId` to the order database table
|
||||||
|
|
||||||
## 2.53.1 - 2024-02-18
|
## 2.53.1 - 2024-02-18
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -99,6 +99,7 @@ We provide official container images hosted on [Docker Hub](https://hub.docker.c
|
|||||||
| `POSTGRES_DB` | | The name of the _PostgreSQL_ database |
|
| `POSTGRES_DB` | | The name of the _PostgreSQL_ database |
|
||||||
| `POSTGRES_PASSWORD` | | The password of the _PostgreSQL_ database |
|
| `POSTGRES_PASSWORD` | | The password of the _PostgreSQL_ database |
|
||||||
| `POSTGRES_USER` | | The user of the _PostgreSQL_ database |
|
| `POSTGRES_USER` | | The user of the _PostgreSQL_ database |
|
||||||
|
| `REDIS_DB` | `0` | The database index of _Redis_ |
|
||||||
| `REDIS_HOST` | | The host where _Redis_ is running |
|
| `REDIS_HOST` | | The host where _Redis_ is running |
|
||||||
| `REDIS_PASSWORD` | | The password of _Redis_ |
|
| `REDIS_PASSWORD` | | The password of _Redis_ |
|
||||||
| `REDIS_PORT` | | The port where _Redis_ is running |
|
| `REDIS_PORT` | | The port where _Redis_ is running |
|
||||||
|
@ -9,12 +9,13 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"executor": "@nx/webpack:webpack",
|
"executor": "@nx/webpack:webpack",
|
||||||
"options": {
|
"options": {
|
||||||
"outputPath": "dist/apps/api",
|
|
||||||
"main": "apps/api/src/main.ts",
|
|
||||||
"tsConfig": "apps/api/tsconfig.app.json",
|
|
||||||
"assets": ["apps/api/src/assets"],
|
|
||||||
"target": "node",
|
|
||||||
"compiler": "tsc",
|
"compiler": "tsc",
|
||||||
|
"deleteOutputPath": false,
|
||||||
|
"main": "apps/api/src/main.ts",
|
||||||
|
"outputPath": "dist/apps/api",
|
||||||
|
"sourceMap": true,
|
||||||
|
"target": "node",
|
||||||
|
"tsConfig": "apps/api/tsconfig.app.json",
|
||||||
"webpackConfig": "apps/api/webpack.config.js"
|
"webpackConfig": "apps/api/webpack.config.js"
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
@ -33,6 +34,23 @@
|
|||||||
},
|
},
|
||||||
"outputs": ["{options.outputPath}"]
|
"outputs": ["{options.outputPath}"]
|
||||||
},
|
},
|
||||||
|
"copy-assets": {
|
||||||
|
"executor": "nx:run-commands",
|
||||||
|
"options": {
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "shx mkdir -p dist/apps/api/assets/locales"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "shx cp -r apps/api/src/assets/* dist/apps/api/assets"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "shx cp -r apps/client/src/locales/* dist/apps/api/assets/locales"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parallel": false
|
||||||
|
}
|
||||||
|
},
|
||||||
"serve": {
|
"serve": {
|
||||||
"executor": "@nx/js:node",
|
"executor": "@nx/js:node",
|
||||||
"options": {
|
"options": {
|
||||||
|
@ -63,7 +63,7 @@ export class AccountController {
|
|||||||
{ Order: true }
|
{ Order: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (account?.isDefault || account?.Order.length > 0) {
|
if (!account || account?.Order.length > 0) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
getReasonPhrase(StatusCodes.FORBIDDEN),
|
getReasonPhrase(StatusCodes.FORBIDDEN),
|
||||||
StatusCodes.FORBIDDEN
|
StatusCodes.FORBIDDEN
|
||||||
|
@ -21,10 +21,8 @@ export class AccountService {
|
|||||||
public async account({
|
public async account({
|
||||||
id_userId
|
id_userId
|
||||||
}: Prisma.AccountWhereUniqueInput): Promise<Account | null> {
|
}: Prisma.AccountWhereUniqueInput): Promise<Account | null> {
|
||||||
const { id, userId } = id_userId;
|
|
||||||
|
|
||||||
const [account] = await this.accounts({
|
const [account] = await this.accounts({
|
||||||
where: { id, userId }
|
where: id_userId
|
||||||
});
|
});
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Transform, TransformFnParams } from 'class-transformer';
|
import { Transform, TransformFnParams } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
|
IsISO4217CurrencyCode,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
IsString,
|
IsString,
|
||||||
@ -19,7 +20,7 @@ export class CreateAccountDto {
|
|||||||
)
|
)
|
||||||
comment?: string;
|
comment?: string;
|
||||||
|
|
||||||
@IsString()
|
@IsISO4217CurrencyCode()
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Transform, TransformFnParams } from 'class-transformer';
|
import { Transform, TransformFnParams } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
|
IsISO4217CurrencyCode,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
IsString,
|
IsString,
|
||||||
@ -19,7 +20,7 @@ export class UpdateAccountDto {
|
|||||||
)
|
)
|
||||||
comment?: string;
|
comment?: string;
|
||||||
|
|
||||||
@IsString()
|
@IsISO4217CurrencyCode()
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ -2,6 +2,7 @@ import { AssetClass, AssetSubClass, Prisma } from '@prisma/client';
|
|||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsEnum,
|
IsEnum,
|
||||||
|
IsISO4217CurrencyCode,
|
||||||
IsObject,
|
IsObject,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
IsString
|
IsString
|
||||||
@ -24,7 +25,7 @@ export class UpdateAssetProfileDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
countries?: Prisma.InputJsonArray;
|
countries?: Prisma.InputJsonArray;
|
||||||
|
|
||||||
@IsString()
|
@IsISO4217CurrencyCode()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
currency?: string;
|
currency?: string;
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ import { UserModule } from './user/user.module';
|
|||||||
BenchmarkModule,
|
BenchmarkModule,
|
||||||
BullModule.forRoot({
|
BullModule.forRoot({
|
||||||
redis: {
|
redis: {
|
||||||
|
db: parseInt(process.env.REDIS_DB ?? '0', 10),
|
||||||
host: process.env.REDIS_HOST,
|
host: process.env.REDIS_HOST,
|
||||||
port: parseInt(process.env.REDIS_PORT ?? '6379', 10),
|
port: parseInt(process.env.REDIS_PORT ?? '6379', 10),
|
||||||
password: process.env.REDIS_PASSWORD
|
password: process.env.REDIS_PASSWORD
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
IsArray,
|
IsArray,
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsEnum,
|
IsEnum,
|
||||||
|
IsISO4217CurrencyCode,
|
||||||
IsISO8601,
|
IsISO8601,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
@ -38,7 +39,7 @@ export class CreateOrderDto {
|
|||||||
)
|
)
|
||||||
comment?: string;
|
comment?: string;
|
||||||
|
|
||||||
@IsString()
|
@IsISO4217CurrencyCode()
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
@ -9,6 +9,7 @@ import { Transform, TransformFnParams } from 'class-transformer';
|
|||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsEnum,
|
IsEnum,
|
||||||
|
IsISO4217CurrencyCode,
|
||||||
IsISO8601,
|
IsISO8601,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
@ -37,7 +38,7 @@ export class UpdateOrderDto {
|
|||||||
)
|
)
|
||||||
comment?: string;
|
comment?: string;
|
||||||
|
|
||||||
@IsString()
|
@IsISO4217CurrencyCode()
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ -118,27 +118,23 @@ export class PortfolioController {
|
|||||||
this.userService.isRestrictedView(this.request.user)
|
this.userService.isRestrictedView(this.request.user)
|
||||||
) {
|
) {
|
||||||
const totalInvestment = Object.values(holdings)
|
const totalInvestment = Object.values(holdings)
|
||||||
.map((portfolioPosition) => {
|
.map(({ investment }) => {
|
||||||
return portfolioPosition.investment;
|
return investment;
|
||||||
})
|
})
|
||||||
.reduce((a, b) => a + b, 0);
|
.reduce((a, b) => a + b, 0);
|
||||||
|
|
||||||
const totalValue = Object.values(holdings)
|
const totalValue = Object.values(holdings)
|
||||||
.map((portfolioPosition) => {
|
.filter(({ assetClass, assetSubClass }) => {
|
||||||
return this.exchangeRateDataService.toCurrency(
|
return assetClass !== 'CASH' && assetSubClass !== 'CASH';
|
||||||
portfolioPosition.quantity * portfolioPosition.marketPrice,
|
})
|
||||||
portfolioPosition.currency,
|
.map(({ valueInBaseCurrency }) => {
|
||||||
this.request.user.Settings.settings.baseCurrency
|
return valueInBaseCurrency;
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.reduce((a, b) => a + b, 0);
|
.reduce((a, b) => a + b, 0);
|
||||||
|
|
||||||
for (const [symbol, portfolioPosition] of Object.entries(holdings)) {
|
for (const [symbol, portfolioPosition] of Object.entries(holdings)) {
|
||||||
portfolioPosition.grossPerformance = null;
|
|
||||||
portfolioPosition.investment =
|
portfolioPosition.investment =
|
||||||
portfolioPosition.investment / totalInvestment;
|
portfolioPosition.investment / totalInvestment;
|
||||||
portfolioPosition.netPerformance = null;
|
|
||||||
portfolioPosition.quantity = null;
|
|
||||||
portfolioPosition.valueInPercentage =
|
portfolioPosition.valueInPercentage =
|
||||||
portfolioPosition.valueInBaseCurrency / totalValue;
|
portfolioPosition.valueInBaseCurrency / totalValue;
|
||||||
}
|
}
|
||||||
@ -429,6 +425,10 @@ export class PortfolioController {
|
|||||||
return nullifyValuesInObject(item, ['totalInvestment', 'value']);
|
return nullifyValuesInObject(item, ['totalInvestment', 'value']);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
performanceInformation.performance = nullifyValuesInObject(
|
||||||
|
performanceInformation.performance,
|
||||||
|
['currentNetPerformance', 'currentNetPerformancePercent']
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return performanceInformation;
|
return performanceInformation;
|
||||||
|
@ -529,12 +529,20 @@ export class PortfolioService {
|
|||||||
grossPerformance: item.grossPerformance?.toNumber() ?? 0,
|
grossPerformance: item.grossPerformance?.toNumber() ?? 0,
|
||||||
grossPerformancePercent:
|
grossPerformancePercent:
|
||||||
item.grossPerformancePercentage?.toNumber() ?? 0,
|
item.grossPerformancePercentage?.toNumber() ?? 0,
|
||||||
|
grossPerformancePercentWithCurrencyEffect:
|
||||||
|
item.grossPerformancePercentageWithCurrencyEffect?.toNumber() ?? 0,
|
||||||
|
grossPerformanceWithCurrencyEffect:
|
||||||
|
item.grossPerformanceWithCurrencyEffect?.toNumber() ?? 0,
|
||||||
investment: item.investment.toNumber(),
|
investment: item.investment.toNumber(),
|
||||||
marketPrice: item.marketPrice,
|
marketPrice: item.marketPrice,
|
||||||
marketState: dataProviderResponse?.marketState ?? 'delayed',
|
marketState: dataProviderResponse?.marketState ?? 'delayed',
|
||||||
name: symbolProfile.name,
|
name: symbolProfile.name,
|
||||||
netPerformance: item.netPerformance?.toNumber() ?? 0,
|
netPerformance: item.netPerformance?.toNumber() ?? 0,
|
||||||
netPerformancePercent: item.netPerformancePercentage?.toNumber() ?? 0,
|
netPerformancePercent: item.netPerformancePercentage?.toNumber() ?? 0,
|
||||||
|
netPerformancePercentWithCurrencyEffect:
|
||||||
|
item.netPerformancePercentageWithCurrencyEffect?.toNumber() ?? 0,
|
||||||
|
netPerformanceWithCurrencyEffect:
|
||||||
|
item.netPerformanceWithCurrencyEffect?.toNumber() ?? 0,
|
||||||
quantity: item.quantity.toNumber(),
|
quantity: item.quantity.toNumber(),
|
||||||
sectors: symbolProfile.sectors,
|
sectors: symbolProfile.sectors,
|
||||||
symbol: item.symbol,
|
symbol: item.symbol,
|
||||||
@ -1600,12 +1608,16 @@ export class PortfolioService {
|
|||||||
dateOfFirstActivity: undefined,
|
dateOfFirstActivity: undefined,
|
||||||
grossPerformance: 0,
|
grossPerformance: 0,
|
||||||
grossPerformancePercent: 0,
|
grossPerformancePercent: 0,
|
||||||
|
grossPerformancePercentWithCurrencyEffect: 0,
|
||||||
|
grossPerformanceWithCurrencyEffect: 0,
|
||||||
investment: balance,
|
investment: balance,
|
||||||
marketPrice: 0,
|
marketPrice: 0,
|
||||||
marketState: 'open',
|
marketState: 'open',
|
||||||
name: currency,
|
name: currency,
|
||||||
netPerformance: 0,
|
netPerformance: 0,
|
||||||
netPerformancePercent: 0,
|
netPerformancePercent: 0,
|
||||||
|
netPerformancePercentWithCurrencyEffect: 0,
|
||||||
|
netPerformanceWithCurrencyEffect: 0,
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
sectors: [],
|
sectors: [],
|
||||||
symbol: currency,
|
symbol: currency,
|
||||||
@ -1814,9 +1826,25 @@ export class PortfolioService {
|
|||||||
})
|
})
|
||||||
?.toNumber();
|
?.toNumber();
|
||||||
|
|
||||||
|
const annualizedPerformancePercentWithCurrencyEffect =
|
||||||
|
new PortfolioCalculator({
|
||||||
|
currency: userCurrency,
|
||||||
|
currentRateService: this.currentRateService,
|
||||||
|
exchangeRateDataService: this.exchangeRateDataService,
|
||||||
|
orders: []
|
||||||
|
})
|
||||||
|
.getAnnualizedPerformancePercent({
|
||||||
|
daysInMarket,
|
||||||
|
netPerformancePercent: new Big(
|
||||||
|
performanceInformation.performance.currentNetPerformancePercentWithCurrencyEffect
|
||||||
|
)
|
||||||
|
})
|
||||||
|
?.toNumber();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...performanceInformation.performance,
|
...performanceInformation.performance,
|
||||||
annualizedPerformancePercent,
|
annualizedPerformancePercent,
|
||||||
|
annualizedPerformancePercentWithCurrencyEffect,
|
||||||
cash,
|
cash,
|
||||||
dividend,
|
dividend,
|
||||||
excludedAccountsAndActivities,
|
excludedAccountsAndActivities,
|
||||||
|
@ -15,6 +15,7 @@ import { RedisCacheService } from './redis-cache.service';
|
|||||||
inject: [ConfigurationService],
|
inject: [ConfigurationService],
|
||||||
useFactory: async (configurationService: ConfigurationService) => {
|
useFactory: async (configurationService: ConfigurationService) => {
|
||||||
return <RedisClientOptions>{
|
return <RedisClientOptions>{
|
||||||
|
db: configurationService.get('REDIS_DB'),
|
||||||
host: configurationService.get('REDIS_HOST'),
|
host: configurationService.get('REDIS_HOST'),
|
||||||
max: configurationService.get('MAX_ITEM_IN_CACHE'),
|
max: configurationService.get('MAX_ITEM_IN_CACHE'),
|
||||||
password: configurationService.get('REDIS_PASSWORD'),
|
password: configurationService.get('REDIS_PASSWORD'),
|
||||||
|
@ -7,6 +7,7 @@ import type {
|
|||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
|
IsISO4217CurrencyCode,
|
||||||
IsISO8601,
|
IsISO8601,
|
||||||
IsIn,
|
IsIn,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
@ -19,8 +20,8 @@ export class UpdateUserSettingDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
annualInterestRate?: number;
|
annualInterestRate?: number;
|
||||||
|
|
||||||
|
@IsISO4217CurrencyCode()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
|
||||||
baseCurrency?: string;
|
baseCurrency?: string;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
|
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
|
||||||
import { environment } from '@ghostfolio/api/environments/environment';
|
import { environment } from '@ghostfolio/api/environments/environment';
|
||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
||||||
|
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
||||||
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
||||||
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
|
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
|
||||||
import {
|
import {
|
||||||
DEFAULT_CURRENCY,
|
DEFAULT_CURRENCY,
|
||||||
|
DEFAULT_LANGUAGE_CODE,
|
||||||
PROPERTY_IS_READ_ONLY_MODE,
|
PROPERTY_IS_READ_ONLY_MODE,
|
||||||
PROPERTY_SYSTEM_MESSAGE,
|
PROPERTY_SYSTEM_MESSAGE,
|
||||||
locale
|
locale
|
||||||
@ -31,6 +33,8 @@ const crypto = require('crypto');
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
|
private i18nService = new I18nService();
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly configurationService: ConfigurationService,
|
private readonly configurationService: ConfigurationService,
|
||||||
private readonly prismaService: PrismaService,
|
private readonly prismaService: PrismaService,
|
||||||
@ -325,8 +329,10 @@ export class UserService {
|
|||||||
Account: {
|
Account: {
|
||||||
create: {
|
create: {
|
||||||
currency: DEFAULT_CURRENCY,
|
currency: DEFAULT_CURRENCY,
|
||||||
isDefault: true,
|
name: this.i18nService.getTranslation({
|
||||||
name: 'Default Account'
|
id: 'myAccount',
|
||||||
|
languageCode: DEFAULT_LANGUAGE_CODE // TODO
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Settings: {
|
Settings: {
|
||||||
@ -438,7 +444,7 @@ export class UserService {
|
|||||||
settings
|
settings
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
userId: userId
|
userId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -51,8 +51,10 @@ export class RedactValuesInResponseInterceptor<T>
|
|||||||
'feeInBaseCurrency',
|
'feeInBaseCurrency',
|
||||||
'filteredValueInBaseCurrency',
|
'filteredValueInBaseCurrency',
|
||||||
'grossPerformance',
|
'grossPerformance',
|
||||||
|
'grossPerformanceWithCurrencyEffect',
|
||||||
'investment',
|
'investment',
|
||||||
'netPerformance',
|
'netPerformance',
|
||||||
|
'netPerformanceWithCurrencyEffect',
|
||||||
'quantity',
|
'quantity',
|
||||||
'symbolMapping',
|
'symbolMapping',
|
||||||
'totalBalanceInBaseCurrency',
|
'totalBalanceInBaseCurrency',
|
||||||
|
@ -43,6 +43,7 @@ export class ConfigurationService {
|
|||||||
MAX_ACTIVITIES_TO_IMPORT: num({ default: Number.MAX_SAFE_INTEGER }),
|
MAX_ACTIVITIES_TO_IMPORT: num({ default: Number.MAX_SAFE_INTEGER }),
|
||||||
MAX_ITEM_IN_CACHE: num({ default: 9999 }),
|
MAX_ITEM_IN_CACHE: num({ default: 9999 }),
|
||||||
PORT: port({ default: 3333 }),
|
PORT: port({ default: 3333 }),
|
||||||
|
REDIS_DB: num({ default: 0 }),
|
||||||
REDIS_HOST: str({ default: 'localhost' }),
|
REDIS_HOST: str({ default: 'localhost' }),
|
||||||
REDIS_PASSWORD: str({ default: '' }),
|
REDIS_PASSWORD: str({ default: '' }),
|
||||||
REDIS_PORT: port({ default: 6379 }),
|
REDIS_PORT: port({ default: 6379 }),
|
||||||
|
@ -37,12 +37,14 @@ export class AlphaVantageService implements DataProviderInterface {
|
|||||||
return !!this.configurationService.get('API_KEY_ALPHA_VANTAGE');
|
return !!this.configurationService.get('API_KEY_ALPHA_VANTAGE');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetProfile(
|
public async getAssetProfile({
|
||||||
aSymbol: string
|
symbol
|
||||||
): Promise<Partial<SymbolProfile>> {
|
}: {
|
||||||
|
symbol: string;
|
||||||
|
}): Promise<Partial<SymbolProfile>> {
|
||||||
return {
|
return {
|
||||||
dataSource: this.getName(),
|
symbol,
|
||||||
symbol: aSymbol
|
dataSource: this.getName()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,15 +52,17 @@ export class CoinGeckoService implements DataProviderInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetProfile(
|
public async getAssetProfile({
|
||||||
aSymbol: string
|
symbol
|
||||||
): Promise<Partial<SymbolProfile>> {
|
}: {
|
||||||
|
symbol: string;
|
||||||
|
}): Promise<Partial<SymbolProfile>> {
|
||||||
const response: Partial<SymbolProfile> = {
|
const response: Partial<SymbolProfile> = {
|
||||||
|
symbol,
|
||||||
assetClass: AssetClass.CASH,
|
assetClass: AssetClass.CASH,
|
||||||
assetSubClass: AssetSubClass.CRYPTOCURRENCY,
|
assetSubClass: AssetSubClass.CRYPTOCURRENCY,
|
||||||
currency: DEFAULT_CURRENCY,
|
currency: DEFAULT_CURRENCY,
|
||||||
dataSource: this.getName(),
|
dataSource: this.getName()
|
||||||
symbol: aSymbol
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -70,7 +72,7 @@ export class CoinGeckoService implements DataProviderInterface {
|
|||||||
abortController.abort();
|
abortController.abort();
|
||||||
}, this.configurationService.get('REQUEST_TIMEOUT'));
|
}, this.configurationService.get('REQUEST_TIMEOUT'));
|
||||||
|
|
||||||
const { name } = await got(`${this.apiUrl}/coins/${aSymbol}`, {
|
const { name } = await got(`${this.apiUrl}/coins/${symbol}`, {
|
||||||
headers: this.headers,
|
headers: this.headers,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
signal: abortController.signal
|
signal: abortController.signal
|
||||||
@ -81,7 +83,7 @@ export class CoinGeckoService implements DataProviderInterface {
|
|||||||
let message = error;
|
let message = error;
|
||||||
|
|
||||||
if (error?.code === 'ABORT_ERR') {
|
if (error?.code === 'ABORT_ERR') {
|
||||||
message = `RequestError: The operation to get the asset profile for ${aSymbol} was aborted because the request to the data provider took more than ${this.configurationService.get(
|
message = `RequestError: The operation to get the asset profile for ${symbol} was aborted because the request to the data provider took more than ${this.configurationService.get(
|
||||||
'REQUEST_TIMEOUT'
|
'REQUEST_TIMEOUT'
|
||||||
)}ms`;
|
)}ms`;
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,9 @@ export class DataProviderService {
|
|||||||
|
|
||||||
for (const symbol of symbols) {
|
for (const symbol of symbols) {
|
||||||
const promise = Promise.resolve(
|
const promise = Promise.resolve(
|
||||||
this.getDataProvider(DataSource[dataSource]).getAssetProfile(symbol)
|
this.getDataProvider(DataSource[dataSource]).getAssetProfile({
|
||||||
|
symbol
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
promises.push(
|
promises.push(
|
||||||
|
@ -46,19 +46,21 @@ export class EodHistoricalDataService implements DataProviderInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetProfile(
|
public async getAssetProfile({
|
||||||
aSymbol: string
|
symbol
|
||||||
): Promise<Partial<SymbolProfile>> {
|
}: {
|
||||||
const [searchResult] = await this.getSearchResult(aSymbol);
|
symbol: string;
|
||||||
|
}): Promise<Partial<SymbolProfile>> {
|
||||||
|
const [searchResult] = await this.getSearchResult(symbol);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
symbol,
|
||||||
assetClass: searchResult?.assetClass,
|
assetClass: searchResult?.assetClass,
|
||||||
assetSubClass: searchResult?.assetSubClass,
|
assetSubClass: searchResult?.assetSubClass,
|
||||||
currency: this.convertCurrency(searchResult?.currency),
|
currency: this.convertCurrency(searchResult?.currency),
|
||||||
dataSource: this.getName(),
|
dataSource: this.getName(),
|
||||||
isin: searchResult?.isin,
|
isin: searchResult?.isin,
|
||||||
name: searchResult?.name,
|
name: searchResult?.name
|
||||||
symbol: aSymbol
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,12 +37,14 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetProfile(
|
public async getAssetProfile({
|
||||||
aSymbol: string
|
symbol
|
||||||
): Promise<Partial<SymbolProfile>> {
|
}: {
|
||||||
|
symbol: string;
|
||||||
|
}): Promise<Partial<SymbolProfile>> {
|
||||||
return {
|
return {
|
||||||
dataSource: this.getName(),
|
symbol,
|
||||||
symbol: aSymbol
|
dataSource: this.getName()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,12 +33,14 @@ export class GoogleSheetsService implements DataProviderInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetProfile(
|
public async getAssetProfile({
|
||||||
aSymbol: string
|
symbol
|
||||||
): Promise<Partial<SymbolProfile>> {
|
}: {
|
||||||
|
symbol: string;
|
||||||
|
}): Promise<Partial<SymbolProfile>> {
|
||||||
return {
|
return {
|
||||||
dataSource: this.getName(),
|
symbol,
|
||||||
symbol: aSymbol
|
dataSource: this.getName()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,11 @@ import { DataSource, SymbolProfile } from '@prisma/client';
|
|||||||
export interface DataProviderInterface {
|
export interface DataProviderInterface {
|
||||||
canHandle(symbol: string): boolean;
|
canHandle(symbol: string): boolean;
|
||||||
|
|
||||||
getAssetProfile(aSymbol: string): Promise<Partial<SymbolProfile>>;
|
getAssetProfile({
|
||||||
|
symbol
|
||||||
|
}: {
|
||||||
|
symbol: string;
|
||||||
|
}): Promise<Partial<SymbolProfile>>;
|
||||||
|
|
||||||
getDataProviderInfo(): DataProviderInfo;
|
getDataProviderInfo(): DataProviderInfo;
|
||||||
|
|
||||||
|
@ -43,16 +43,18 @@ export class ManualService implements DataProviderInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetProfile(
|
public async getAssetProfile({
|
||||||
aSymbol: string
|
symbol
|
||||||
): Promise<Partial<SymbolProfile>> {
|
}: {
|
||||||
|
symbol: string;
|
||||||
|
}): Promise<Partial<SymbolProfile>> {
|
||||||
const assetProfile: Partial<SymbolProfile> = {
|
const assetProfile: Partial<SymbolProfile> = {
|
||||||
dataSource: this.getName(),
|
symbol,
|
||||||
symbol: aSymbol
|
dataSource: this.getName()
|
||||||
};
|
};
|
||||||
|
|
||||||
const [symbolProfile] = await this.symbolProfileService.getSymbolProfiles([
|
const [symbolProfile] = await this.symbolProfileService.getSymbolProfiles([
|
||||||
{ dataSource: this.getName(), symbol: aSymbol }
|
{ symbol, dataSource: this.getName() }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (symbolProfile) {
|
if (symbolProfile) {
|
||||||
|
@ -30,12 +30,14 @@ export class RapidApiService implements DataProviderInterface {
|
|||||||
return !!this.configurationService.get('API_KEY_RAPID_API');
|
return !!this.configurationService.get('API_KEY_RAPID_API');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetProfile(
|
public async getAssetProfile({
|
||||||
aSymbol: string
|
symbol
|
||||||
): Promise<Partial<SymbolProfile>> {
|
}: {
|
||||||
|
symbol: string;
|
||||||
|
}): Promise<Partial<SymbolProfile>> {
|
||||||
return {
|
return {
|
||||||
dataSource: this.getName(),
|
symbol,
|
||||||
symbol: aSymbol
|
dataSource: this.getName()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,11 +33,13 @@ export class YahooFinanceService implements DataProviderInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetProfile(
|
public async getAssetProfile({
|
||||||
aSymbol: string
|
symbol
|
||||||
): Promise<Partial<SymbolProfile>> {
|
}: {
|
||||||
const { assetClass, assetSubClass, currency, name, symbol } =
|
symbol: string;
|
||||||
await this.yahooFinanceDataEnhancerService.getAssetProfile(aSymbol);
|
}): Promise<Partial<SymbolProfile>> {
|
||||||
|
const { assetClass, assetSubClass, currency, name } =
|
||||||
|
await this.yahooFinanceDataEnhancerService.getAssetProfile(symbol);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
assetClass,
|
assetClass,
|
||||||
|
@ -451,7 +451,7 @@ export class ExchangeRateDataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async prepareCurrencies(): Promise<string[]> {
|
private async prepareCurrencies(): Promise<string[]> {
|
||||||
let currencies: string[] = [];
|
let currencies: string[] = [DEFAULT_CURRENCY];
|
||||||
|
|
||||||
(
|
(
|
||||||
await this.prismaService.account.findMany({
|
await this.prismaService.account.findMany({
|
||||||
|
@ -30,6 +30,7 @@ export interface Environment extends CleanedEnvAccessors {
|
|||||||
MAX_ACTIVITIES_TO_IMPORT: number;
|
MAX_ACTIVITIES_TO_IMPORT: number;
|
||||||
MAX_ITEM_IN_CACHE: number;
|
MAX_ITEM_IN_CACHE: number;
|
||||||
PORT: number;
|
PORT: number;
|
||||||
|
REDIS_DB: number;
|
||||||
REDIS_HOST: string;
|
REDIS_HOST: string;
|
||||||
REDIS_PASSWORD: string;
|
REDIS_PASSWORD: string;
|
||||||
REDIS_PORT: number;
|
REDIS_PORT: number;
|
||||||
|
@ -13,13 +13,13 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"executor": "@nx/angular:webpack-browser",
|
"executor": "@nx/angular:webpack-browser",
|
||||||
"options": {
|
"options": {
|
||||||
|
"deleteOutputPath": false,
|
||||||
"localize": true,
|
"localize": true,
|
||||||
"outputPath": "dist/apps/client",
|
"outputPath": "dist/apps/client",
|
||||||
"index": "apps/client/src/index.html",
|
"index": "apps/client/src/index.html",
|
||||||
"main": "apps/client/src/main.ts",
|
"main": "apps/client/src/main.ts",
|
||||||
"polyfills": "apps/client/src/polyfills.ts",
|
"polyfills": "apps/client/src/polyfills.ts",
|
||||||
"tsConfig": "apps/client/tsconfig.app.json",
|
"tsConfig": "apps/client/tsconfig.app.json",
|
||||||
"assets": [],
|
|
||||||
"styles": [
|
"styles": [
|
||||||
"apps/client/src/assets/fonts/inter.css",
|
"apps/client/src/assets/fonts/inter.css",
|
||||||
"apps/client/src/styles/theme.scss",
|
"apps/client/src/styles/theme.scss",
|
||||||
@ -108,13 +108,19 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"commands": [
|
"commands": [
|
||||||
{
|
{
|
||||||
"command": "shx mkdir -p dist/apps/client"
|
"command": "shx mkdir -p dist/apps/client/.well-known"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "shx cp -r apps/client/src/assets dist/apps/client"
|
"command": "shx mkdir -p dist/apps/client/assets"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "shx cp -r apps/client/src/assets/.well-known dist/apps/client"
|
"command": "shx mkdir -p dist/apps/client/ionicons"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "shx cp -r apps/client/src/assets/* dist/apps/client/assets"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "shx cp -r apps/client/src/assets/.well-known/* dist/apps/client/.well-known"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "shx cp apps/client/src/assets/favicon.ico dist/apps/client"
|
"command": "shx cp apps/client/src/assets/favicon.ico dist/apps/client"
|
||||||
@ -128,9 +134,6 @@
|
|||||||
{
|
{
|
||||||
"command": "shx cp apps/client/src/assets/site.webmanifest dist/apps/client"
|
"command": "shx cp apps/client/src/assets/site.webmanifest dist/apps/client"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"command": "shx cp -r apps/client/src/locales dist/apps/api/assets"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"command": "shx cp node_modules/ionicons/dist/index.js dist/apps/client"
|
"command": "shx cp node_modules/ionicons/dist/index.js dist/apps/client"
|
||||||
},
|
},
|
||||||
@ -138,7 +141,7 @@
|
|||||||
"command": "shx cp node_modules/ionicons/dist/ionicons.js dist/apps/client"
|
"command": "shx cp node_modules/ionicons/dist/ionicons.js dist/apps/client"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "shx cp -r node_modules/ionicons/dist/ionicons dist/apps/client/ionicons"
|
"command": "shx cp -r node_modules/ionicons/dist/ionicons/* dist/apps/client/ionicons"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "shx cp CHANGELOG.md dist/apps/client/assets"
|
"command": "shx cp CHANGELOG.md dist/apps/client/assets"
|
||||||
@ -146,7 +149,8 @@
|
|||||||
{
|
{
|
||||||
"command": "shx cp LICENSE dist/apps/client/assets"
|
"command": "shx cp LICENSE dist/apps/client/assets"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"parallel": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"serve": {
|
"serve": {
|
||||||
|
@ -25,7 +25,9 @@
|
|||||||
class="h-100"
|
class="h-100"
|
||||||
[currency]="user?.settings?.baseCurrency"
|
[currency]="user?.settings?.baseCurrency"
|
||||||
[historicalDataItems]="historicalDataItems"
|
[historicalDataItems]="historicalDataItems"
|
||||||
[isInPercent]="data.hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="
|
||||||
|
data.hasImpersonationId || user.settings.isRestrictedView
|
||||||
|
"
|
||||||
[isLoading]="isLoadingChart"
|
[isLoading]="isLoadingChart"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
/>
|
/>
|
||||||
@ -92,7 +94,9 @@
|
|||||||
[dataSource]="dataSource"
|
[dataSource]="dataSource"
|
||||||
[deviceType]="data.deviceType"
|
[deviceType]="data.deviceType"
|
||||||
[hasPermissionToCreateActivity]="false"
|
[hasPermissionToCreateActivity]="false"
|
||||||
[hasPermissionToExportActivities]="!data.hasImpersonationId && !user.settings.isRestrictedView"
|
[hasPermissionToExportActivities]="
|
||||||
|
!data.hasImpersonationId && !user.settings.isRestrictedView
|
||||||
|
"
|
||||||
[hasPermissionToFilter]="false"
|
[hasPermissionToFilter]="false"
|
||||||
[hasPermissionToOpenDetails]="false"
|
[hasPermissionToOpenDetails]="false"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
@ -113,7 +117,11 @@
|
|||||||
[accountBalances]="accountBalances"
|
[accountBalances]="accountBalances"
|
||||||
[accountId]="data.accountId"
|
[accountId]="data.accountId"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[showActions]="!data.hasImpersonationId && hasPermissionToDeleteAccountBalance && !user.settings.isRestrictedView"
|
[showActions]="
|
||||||
|
!data.hasImpersonationId &&
|
||||||
|
hasPermissionToDeleteAccountBalance &&
|
||||||
|
!user.settings.isRestrictedView
|
||||||
|
"
|
||||||
(accountBalanceDeleted)="onDeleteAccountBalance($event)"
|
(accountBalanceDeleted)="onDeleteAccountBalance($event)"
|
||||||
/>
|
/>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
|
@ -40,12 +40,7 @@
|
|||||||
[tooltip]="element.Platform?.name"
|
[tooltip]="element.Platform?.name"
|
||||||
[url]="element.Platform?.url"
|
[url]="element.Platform?.url"
|
||||||
/>
|
/>
|
||||||
<span>{{ element.name }} </span>
|
<span>{{ element.name }}</span>
|
||||||
<span
|
|
||||||
*ngIf="element.isDefault"
|
|
||||||
class="d-lg-inline-block d-none text-muted"
|
|
||||||
>(Default)</span
|
|
||||||
>
|
|
||||||
</td>
|
</td>
|
||||||
<td *matFooterCellDef class="px-1" i18n mat-footer-cell>Total</td>
|
<td *matFooterCellDef class="px-1" i18n mat-footer-cell>Total</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@ -261,7 +256,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[disabled]="element.isDefault || element.transactionCount > 0"
|
[disabled]="element.transactionCount > 0"
|
||||||
(click)="onDeleteAccount(element.id)"
|
(click)="onDeleteAccount(element.id)"
|
||||||
>
|
>
|
||||||
<span class="align-items-center d-flex">
|
<span class="align-items-center d-flex">
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { getLocale } from '@ghostfolio/common/helper';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
@ -27,7 +29,7 @@ export class AccountsTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
@Input() baseCurrency: string;
|
@Input() baseCurrency: string;
|
||||||
@Input() deviceType: string;
|
@Input() deviceType: string;
|
||||||
@Input() hasPermissionToOpenDetails = true;
|
@Input() hasPermissionToOpenDetails = true;
|
||||||
@Input() locale: string;
|
@Input() locale = getLocale();
|
||||||
@Input() showActions: boolean;
|
@Input() showActions: boolean;
|
||||||
@Input() showBalance = true;
|
@Input() showBalance = true;
|
||||||
@Input() showFooter = true;
|
@Input() showFooter = true;
|
||||||
|
@ -163,7 +163,12 @@
|
|||||||
<mat-menu #assetProfileActionsMenu="matMenu" xPosition="before">
|
<mat-menu #assetProfileActionsMenu="matMenu" xPosition="before">
|
||||||
<button
|
<button
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
(click)="onOpenAssetProfileDialog({ dataSource: element.dataSource, symbol: element.symbol })"
|
(click)="
|
||||||
|
onOpenAssetProfileDialog({
|
||||||
|
dataSource: element.dataSource,
|
||||||
|
symbol: element.symbol
|
||||||
|
})
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<span class="align-items-center d-flex">
|
<span class="align-items-center d-flex">
|
||||||
<ion-icon class="mr-2" name="create-outline" />
|
<ion-icon class="mr-2" name="create-outline" />
|
||||||
@ -173,7 +178,12 @@
|
|||||||
<button
|
<button
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[disabled]="element.activitiesCount !== 0"
|
[disabled]="element.activitiesCount !== 0"
|
||||||
(click)="onDeleteProfileData({dataSource: element.dataSource, symbol: element.symbol})"
|
(click)="
|
||||||
|
onDeleteProfileData({
|
||||||
|
dataSource: element.dataSource,
|
||||||
|
symbol: element.symbol
|
||||||
|
})
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<span class="align-items-center d-flex">
|
<span class="align-items-center d-flex">
|
||||||
<ion-icon class="mr-2" name="trash-outline" />
|
<ion-icon class="mr-2" name="trash-outline" />
|
||||||
@ -189,16 +199,19 @@
|
|||||||
*matRowDef="let row; columns: displayedColumns"
|
*matRowDef="let row; columns: displayedColumns"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
mat-row
|
mat-row
|
||||||
(click)="onOpenAssetProfileDialog({ dataSource: row.dataSource, symbol: row.symbol })"
|
(click)="
|
||||||
|
onOpenAssetProfileDialog({
|
||||||
|
dataSource: row.dataSource,
|
||||||
|
symbol: row.symbol
|
||||||
|
})
|
||||||
|
"
|
||||||
></tr>
|
></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<mat-paginator
|
<mat-paginator
|
||||||
[length]="totalItems"
|
[length]="totalItems"
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
'd-none':
|
'd-none': (isLoading && totalItems === 0) || totalItems <= pageSize
|
||||||
(isLoading && totalItems === 0) ||
|
|
||||||
totalItems <= pageSize
|
|
||||||
}"
|
}"
|
||||||
[pageSize]="pageSize"
|
[pageSize]="pageSize"
|
||||||
[showFirstLastButtons]="true"
|
[showFirstLastButtons]="true"
|
||||||
|
@ -25,7 +25,9 @@
|
|||||||
mat-menu-item
|
mat-menu-item
|
||||||
type="button"
|
type="button"
|
||||||
[disabled]="assetProfileForm.dirty"
|
[disabled]="assetProfileForm.dirty"
|
||||||
(click)="onGatherSymbol({dataSource: data.dataSource, symbol: data.symbol})"
|
(click)="
|
||||||
|
onGatherSymbol({ dataSource: data.dataSource, symbol: data.symbol })
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<ng-container i18n>Gather Historical Data</ng-container>
|
<ng-container i18n>Gather Historical Data</ng-container>
|
||||||
</button>
|
</button>
|
||||||
@ -33,7 +35,12 @@
|
|||||||
mat-menu-item
|
mat-menu-item
|
||||||
type="button"
|
type="button"
|
||||||
[disabled]="assetProfileForm.dirty"
|
[disabled]="assetProfileForm.dirty"
|
||||||
(click)="onGatherProfileDataBySymbol({dataSource: data.dataSource, symbol: data.symbol})"
|
(click)="
|
||||||
|
onGatherProfileDataBySymbol({
|
||||||
|
dataSource: data.dataSource,
|
||||||
|
symbol: data.symbol
|
||||||
|
})
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<ng-container i18n>Gather Profile Data</ng-container>
|
<ng-container i18n>Gather Profile Data</ng-container>
|
||||||
</button>
|
</button>
|
||||||
@ -73,7 +80,12 @@
|
|||||||
color="accent"
|
color="accent"
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
type="button"
|
type="button"
|
||||||
[disabled]="!assetProfileForm.controls['historicalData']?.controls['csvString'].touched || assetProfileForm.controls['historicalData']?.controls['csvString']?.value === ''"
|
[disabled]="
|
||||||
|
!assetProfileForm.controls['historicalData']?.controls['csvString']
|
||||||
|
.touched ||
|
||||||
|
assetProfileForm.controls['historicalData']?.controls['csvString']
|
||||||
|
?.value === ''
|
||||||
|
"
|
||||||
(click)="onImportHistoricalData()"
|
(click)="onImportHistoricalData()"
|
||||||
>
|
>
|
||||||
<ng-container i18n>Import</ng-container>
|
<ng-container i18n>Import</ng-container>
|
||||||
@ -129,49 +141,54 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngIf="assetProfile?.countries?.length > 0 || assetProfile?.sectors?.length > 0"
|
*ngIf="
|
||||||
|
assetProfile?.countries?.length > 0 ||
|
||||||
|
assetProfile?.sectors?.length > 0
|
||||||
|
"
|
||||||
>
|
>
|
||||||
@if (assetProfile?.countries?.length === 1 &&
|
@if (
|
||||||
assetProfile?.sectors?.length === 1 ) {
|
assetProfile?.countries?.length === 1 &&
|
||||||
<div *ngIf="assetProfile?.sectors?.length === 1" class="col-6 mb-3">
|
assetProfile?.sectors?.length === 1
|
||||||
<gf-value
|
) {
|
||||||
i18n
|
<div *ngIf="assetProfile?.sectors?.length === 1" class="col-6 mb-3">
|
||||||
size="medium"
|
<gf-value
|
||||||
[locale]="data.locale"
|
i18n
|
||||||
[value]="assetProfile?.sectors[0].name"
|
size="medium"
|
||||||
>Sector</gf-value
|
[locale]="data.locale"
|
||||||
>
|
[value]="assetProfile?.sectors[0].name"
|
||||||
</div>
|
>Sector</gf-value
|
||||||
<div *ngIf="assetProfile?.countries?.length === 1" class="col-6 mb-3">
|
>
|
||||||
<gf-value
|
</div>
|
||||||
i18n
|
<div *ngIf="assetProfile?.countries?.length === 1" class="col-6 mb-3">
|
||||||
size="medium"
|
<gf-value
|
||||||
[locale]="data.locale"
|
i18n
|
||||||
[value]="assetProfile?.countries[0].name"
|
size="medium"
|
||||||
>Country</gf-value
|
[locale]="data.locale"
|
||||||
>
|
[value]="assetProfile?.countries[0].name"
|
||||||
</div>
|
>Country</gf-value
|
||||||
|
>
|
||||||
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="h5" i18n>Sectors</div>
|
<div class="h5" i18n>Sectors</div>
|
||||||
<gf-portfolio-proportion-chart
|
<gf-portfolio-proportion-chart
|
||||||
[colorScheme]="data.colorScheme"
|
[colorScheme]="data.colorScheme"
|
||||||
[isInPercent]="true"
|
[isInPercent]="true"
|
||||||
[keys]="['name']"
|
[keys]="['name']"
|
||||||
[maxItems]="10"
|
[maxItems]="10"
|
||||||
[positions]="sectors"
|
[positions]="sectors"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="h5" i18n>Countries</div>
|
<div class="h5" i18n>Countries</div>
|
||||||
<gf-portfolio-proportion-chart
|
<gf-portfolio-proportion-chart
|
||||||
[colorScheme]="data.colorScheme"
|
[colorScheme]="data.colorScheme"
|
||||||
[isInPercent]="true"
|
[isInPercent]="true"
|
||||||
[keys]="['name']"
|
[keys]="['name']"
|
||||||
[maxItems]="10"
|
[maxItems]="10"
|
||||||
[positions]="countries"
|
[positions]="countries"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
@ -222,7 +239,17 @@
|
|||||||
color="primary"
|
color="primary"
|
||||||
i18n
|
i18n
|
||||||
[checked]="isBenchmark"
|
[checked]="isBenchmark"
|
||||||
(change)="isBenchmark ? onUnsetBenchmark({dataSource: data.dataSource, symbol: data.symbol}) : onSetBenchmark({dataSource: data.dataSource, symbol: data.symbol})"
|
(change)="
|
||||||
|
isBenchmark
|
||||||
|
? onUnsetBenchmark({
|
||||||
|
dataSource: data.dataSource,
|
||||||
|
symbol: data.symbol
|
||||||
|
})
|
||||||
|
: onSetBenchmark({
|
||||||
|
dataSource: data.dataSource,
|
||||||
|
symbol: data.symbol
|
||||||
|
})
|
||||||
|
"
|
||||||
>Benchmark</mat-checkbox
|
>Benchmark</mat-checkbox
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@ -253,7 +280,9 @@
|
|||||||
color="accent"
|
color="accent"
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
type="button"
|
type="button"
|
||||||
[disabled]="assetProfileForm.controls['scraperConfiguration'].value === '{}'"
|
[disabled]="
|
||||||
|
assetProfileForm.controls['scraperConfiguration'].value === '{}'
|
||||||
|
"
|
||||||
(click)="onTestMarketData()"
|
(click)="onTestMarketData()"
|
||||||
>
|
>
|
||||||
<ng-container i18n>Test</ng-container>
|
<ng-container i18n>Test</ng-container>
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
[value]="transactionCount"
|
[value]="transactionCount"
|
||||||
/>
|
/>
|
||||||
<div *ngIf="transactionCount && userCount">
|
<div *ngIf="transactionCount && userCount">
|
||||||
{{ transactionCount / userCount | number : '1.2-2' }}
|
{{ transactionCount / userCount | number: '1.2-2' }}
|
||||||
<span i18n>per User</span>
|
<span i18n>per User</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -69,10 +69,10 @@
|
|||||||
<a
|
<a
|
||||||
mat-menu-item
|
mat-menu-item
|
||||||
[queryParams]="{
|
[queryParams]="{
|
||||||
assetProfileDialog: true,
|
assetProfileDialog: true,
|
||||||
dataSource: exchangeRate.dataSource,
|
dataSource: exchangeRate.dataSource,
|
||||||
symbol: exchangeRate.symbol
|
symbol: exchangeRate.symbol
|
||||||
}"
|
}"
|
||||||
[routerLink]="['/admin', 'market-data']"
|
[routerLink]="['/admin', 'market-data']"
|
||||||
>
|
>
|
||||||
<span class="align-items-center d-flex">
|
<span class="align-items-center d-flex">
|
||||||
@ -112,7 +112,9 @@
|
|||||||
<mat-slide-toggle
|
<mat-slide-toggle
|
||||||
color="primary"
|
color="primary"
|
||||||
hideIcon="true"
|
hideIcon="true"
|
||||||
[checked]="info.globalPermissions.includes(permissions.createUserAccount)"
|
[checked]="
|
||||||
|
info.globalPermissions.includes(permissions.createUserAccount)
|
||||||
|
"
|
||||||
(change)="onEnableUserSignupModeChange($event)"
|
(change)="onEnableUserSignupModeChange($event)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -143,7 +145,7 @@
|
|||||||
<div class="w-50" i18n>System Message</div>
|
<div class="w-50" i18n>System Message</div>
|
||||||
<div class="w-50">
|
<div class="w-50">
|
||||||
<div *ngIf="systemMessage" class="align-items-center d-flex">
|
<div *ngIf="systemMessage" class="align-items-center d-flex">
|
||||||
<div class="text-truncate">{{ systemMessage | json }}</div>
|
<div class="text-truncate">{{ systemMessage | json }}</div>
|
||||||
<button
|
<button
|
||||||
class="h-100 mx-1 no-min-width px-2"
|
class="h-100 mx-1 no-min-width px-2"
|
||||||
mat-button
|
mat-button
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
#
|
#
|
||||||
</th>
|
</th>
|
||||||
<td
|
<td
|
||||||
*matCellDef="let element; let i=index"
|
*matCellDef="let element; let i = index"
|
||||||
class="mat-mdc-cell px-1 py-2 text-right"
|
class="mat-mdc-cell px-1 py-2 text-right"
|
||||||
mat-cell
|
mat-cell
|
||||||
>
|
>
|
||||||
@ -35,17 +35,23 @@
|
|||||||
mat-cell
|
mat-cell
|
||||||
>
|
>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="d-none d-sm-inline-block text-monospace"
|
<span class="d-none d-sm-inline-block text-monospace">{{
|
||||||
>{{ element.id }}</span
|
element.id
|
||||||
>
|
}}</span>
|
||||||
<span class="d-inline-block d-sm-none text-monospace"
|
<span class="d-inline-block d-sm-none text-monospace">{{
|
||||||
>{{ (element.id | slice:0:5) + '...' }}</span
|
(element.id | slice: 0 : 5) + '...'
|
||||||
>
|
}}</span>
|
||||||
<gf-premium-indicator
|
<gf-premium-indicator
|
||||||
*ngIf="element?.subscription?.type === 'Premium'"
|
*ngIf="element?.subscription?.type === 'Premium'"
|
||||||
class="ml-1"
|
class="ml-1"
|
||||||
[enableLink]="false"
|
[enableLink]="false"
|
||||||
[title]="'Expires ' + formatDistanceToNow(element.subscription.expiresAt) + ' (' + (element.subscription.expiresAt | date: defaultDateFormat) + ')'"
|
[title]="
|
||||||
|
'Expires ' +
|
||||||
|
formatDistanceToNow(element.subscription.expiresAt) +
|
||||||
|
' (' +
|
||||||
|
(element.subscription.expiresAt | date: defaultDateFormat) +
|
||||||
|
')'
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -67,9 +73,9 @@
|
|||||||
class="mat-mdc-cell px-1 py-2"
|
class="mat-mdc-cell px-1 py-2"
|
||||||
mat-cell
|
mat-cell
|
||||||
>
|
>
|
||||||
<span class="h5" [title]="element.country"
|
<span class="h5" [title]="element.country">{{
|
||||||
>{{ getEmojiFlag(element.country) }}</span
|
getEmojiFlag(element.country)
|
||||||
>
|
}}</span>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
|
|||||||
import {
|
import {
|
||||||
getBackgroundColor,
|
getBackgroundColor,
|
||||||
getDateFormatString,
|
getDateFormatString,
|
||||||
|
getLocale,
|
||||||
getTextColor,
|
getTextColor,
|
||||||
parseDate
|
parseDate
|
||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
@ -51,7 +52,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
@Input() colorScheme: ColorScheme;
|
@Input() colorScheme: ColorScheme;
|
||||||
@Input() daysInMarket: number;
|
@Input() daysInMarket: number;
|
||||||
@Input() isLoading: boolean;
|
@Input() isLoading: boolean;
|
||||||
@Input() locale: string;
|
@Input() locale = getLocale();
|
||||||
@Input() performanceDataItems: LineChartItem[];
|
@Input() performanceDataItems: LineChartItem[];
|
||||||
@Input() user: User;
|
@Input() user: User;
|
||||||
|
|
||||||
|
@ -154,8 +154,8 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
|||||||
this.dataService
|
this.dataService
|
||||||
.fetchPositions({ range: this.user?.settings?.dateRange })
|
.fetchPositions({ range: this.user?.settings?.dateRange })
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((response) => {
|
.subscribe(({ positions }) => {
|
||||||
this.positions = response.positions;
|
this.positions = positions;
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
@ -127,10 +127,10 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
|
|||||||
this.isLoadingPerformance = false;
|
this.isLoadingPerformance = false;
|
||||||
|
|
||||||
this.historicalDataItems = chart.map(
|
this.historicalDataItems = chart.map(
|
||||||
({ date, netPerformanceInPercentage }) => {
|
({ date, netPerformanceInPercentageWithCurrencyEffect }) => {
|
||||||
return {
|
return {
|
||||||
date,
|
date,
|
||||||
value: netPerformanceInPercentage
|
value: netPerformanceInPercentageWithCurrencyEffect
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1,100 +1,100 @@
|
|||||||
<div
|
<div
|
||||||
class="align-items-center container d-flex flex-column h-100 justify-content-center overview p-0 position-relative"
|
class="align-items-center container d-flex flex-column h-100 justify-content-center overview p-0 position-relative"
|
||||||
>
|
>
|
||||||
@if(hasPermissionToCreateOrder && historicalDataItems?.length === 0) {
|
@if (hasPermissionToCreateOrder && historicalDataItems?.length === 0) {
|
||||||
<div class="justify-content-center row w-100">
|
<div class="justify-content-center row w-100">
|
||||||
<div class="col introduction">
|
<div class="col introduction">
|
||||||
<h4 i18n>Welcome to Ghostfolio</h4>
|
<h4 i18n>Welcome to Ghostfolio</h4>
|
||||||
<p i18n>Ready to take control of your personal finances?</p>
|
<p i18n>Ready to take control of your personal finances?</p>
|
||||||
<ol class="font-weight-bold">
|
<ol class="font-weight-bold">
|
||||||
<li
|
<li
|
||||||
class="mb-2"
|
class="mb-2"
|
||||||
[ngClass]="{ 'text-muted': user?.accounts?.length > 1 }"
|
[ngClass]="{ 'text-muted': user?.accounts?.length > 1 }"
|
||||||
>
|
|
||||||
<a class="d-block" [routerLink]="['/accounts']"
|
|
||||||
><span i18n>Setup your accounts</span><br />
|
|
||||||
<span class="font-weight-normal" i18n
|
|
||||||
>Get a comprehensive financial overview by adding your bank and
|
|
||||||
brokerage accounts.</span
|
|
||||||
></a
|
|
||||||
>
|
>
|
||||||
</li>
|
<a class="d-block" [routerLink]="['/accounts']"
|
||||||
<li class="mb-2">
|
><span i18n>Setup your accounts</span><br />
|
||||||
<a class="d-block" [routerLink]="['/portfolio', 'activities']">
|
<span class="font-weight-normal" i18n
|
||||||
<span i18n>Capture your activities</span><br />
|
>Get a comprehensive financial overview by adding your bank and
|
||||||
<span class="font-weight-normal" i18n
|
brokerage accounts.</span
|
||||||
>Record your investment activities to keep your portfolio up to
|
></a
|
||||||
date.</span
|
|
||||||
></a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li class="mb-2">
|
|
||||||
<a class="d-block" [routerLink]="['/portfolio']">
|
|
||||||
<span i18n>Monitor and analyze your portfolio</span><br />
|
|
||||||
<span class="font-weight-normal" i18n
|
|
||||||
>Track your progress in real-time with comprehensive analysis and
|
|
||||||
insights.</span
|
|
||||||
>
|
>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<a class="d-block" [routerLink]="['/portfolio', 'activities']">
|
||||||
|
<span i18n>Capture your activities</span><br />
|
||||||
|
<span class="font-weight-normal" i18n
|
||||||
|
>Record your investment activities to keep your portfolio up to
|
||||||
|
date.</span
|
||||||
|
></a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<a class="d-block" [routerLink]="['/portfolio']">
|
||||||
|
<span i18n>Monitor and analyze your portfolio</span><br />
|
||||||
|
<span class="font-weight-normal" i18n
|
||||||
|
>Track your progress in real-time with comprehensive analysis
|
||||||
|
and insights.</span
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<a
|
||||||
|
*ngIf="user?.accounts?.length === 1"
|
||||||
|
color="primary"
|
||||||
|
mat-flat-button
|
||||||
|
[routerLink]="['/accounts']"
|
||||||
|
>
|
||||||
|
<ng-container i18n>Setup accounts</ng-container>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
<a
|
||||||
</ol>
|
*ngIf="user?.accounts?.length > 1"
|
||||||
<div class="d-flex justify-content-center">
|
color="primary"
|
||||||
<a
|
mat-flat-button
|
||||||
*ngIf="user?.accounts?.length === 1"
|
[routerLink]="['/portfolio', 'activities']"
|
||||||
color="primary"
|
>
|
||||||
mat-flat-button
|
<ng-container i18n>Add activity</ng-container>
|
||||||
[routerLink]="['/accounts']"
|
</a>
|
||||||
>
|
</div>
|
||||||
<ng-container i18n>Setup accounts</ng-container>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
*ngIf="user?.accounts?.length > 1"
|
|
||||||
color="primary"
|
|
||||||
mat-flat-button
|
|
||||||
[routerLink]="['/portfolio', 'activities']"
|
|
||||||
>
|
|
||||||
<ng-container i18n>Add activity</ng-container>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
} @else {
|
} @else {
|
||||||
<div class="row w-100">
|
<div class="row w-100">
|
||||||
<div class="col p-0">
|
<div class="col p-0">
|
||||||
<div class="chart-container mx-auto position-relative">
|
<div class="chart-container mx-auto position-relative">
|
||||||
<gf-line-chart
|
<gf-line-chart
|
||||||
class="position-absolute"
|
class="position-absolute"
|
||||||
symbol="Performance"
|
symbol="Performance"
|
||||||
unit="%"
|
unit="%"
|
||||||
[colorScheme]="user?.settings?.colorScheme"
|
[colorScheme]="user?.settings?.colorScheme"
|
||||||
[hidden]="historicalDataItems?.length === 0"
|
[hidden]="historicalDataItems?.length === 0"
|
||||||
[historicalDataItems]="historicalDataItems"
|
[historicalDataItems]="historicalDataItems"
|
||||||
[isAnimated]="user?.settings?.dateRange === '1d' ? false : true"
|
[isAnimated]="user?.settings?.dateRange === '1d' ? false : true"
|
||||||
|
[locale]="user?.settings?.locale"
|
||||||
|
[ngClass]="{ 'pr-3': deviceType === 'mobile' }"
|
||||||
|
[showGradient]="true"
|
||||||
|
[showLoader]="false"
|
||||||
|
[showXAxis]="false"
|
||||||
|
[showYAxis]="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="overview-container row mt-1">
|
||||||
|
<div class="col">
|
||||||
|
<gf-portfolio-performance
|
||||||
|
class="pb-4"
|
||||||
|
[deviceType]="deviceType"
|
||||||
|
[errors]="errors"
|
||||||
|
[isAllTimeHigh]="isAllTimeHigh"
|
||||||
|
[isAllTimeLow]="isAllTimeLow"
|
||||||
|
[isLoading]="isLoadingPerformance"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[ngClass]="{ 'pr-3': deviceType === 'mobile' }"
|
[performance]="performance"
|
||||||
[showGradient]="true"
|
[showDetails]="showDetails"
|
||||||
[showLoader]="false"
|
[unit]="unit"
|
||||||
[showXAxis]="false"
|
|
||||||
[showYAxis]="false"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="overview-container row mt-1">
|
|
||||||
<div class="col">
|
|
||||||
<gf-portfolio-performance
|
|
||||||
class="pb-4"
|
|
||||||
[deviceType]="deviceType"
|
|
||||||
[errors]="errors"
|
|
||||||
[isAllTimeHigh]="isAllTimeHigh"
|
|
||||||
[isAllTimeLow]="isAllTimeLow"
|
|
||||||
[isLoading]="isLoadingPerformance"
|
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
[performance]="performance"
|
|
||||||
[showDetails]="showDetails"
|
|
||||||
[unit]="unit"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<gf-portfolio-summary
|
<gf-portfolio-summary
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
[hasPermissionToUpdateUserSettings]="!hasImpersonationId && hasPermissionToUpdateUserSettings"
|
[hasPermissionToUpdateUserSettings]="
|
||||||
|
!hasImpersonationId && hasPermissionToUpdateUserSettings
|
||||||
|
"
|
||||||
[isLoading]="isLoading"
|
[isLoading]="isLoading"
|
||||||
[language]="user?.settings?.language"
|
[language]="user?.settings?.language"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
DATE_FORMAT,
|
DATE_FORMAT,
|
||||||
getBackgroundColor,
|
getBackgroundColor,
|
||||||
getDateFormatString,
|
getDateFormatString,
|
||||||
|
getLocale,
|
||||||
getTextColor,
|
getTextColor,
|
||||||
parseDate
|
parseDate
|
||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
@ -65,7 +66,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
@Input() historicalDataItems: LineChartItem[] = [];
|
@Input() historicalDataItems: LineChartItem[] = [];
|
||||||
@Input() isInPercent = false;
|
@Input() isInPercent = false;
|
||||||
@Input() isLoading = false;
|
@Input() isLoading = false;
|
||||||
@Input() locale: string;
|
@Input() locale = getLocale();
|
||||||
@Input() range: DateRange = 'max';
|
@Input() range: DateRange = 'max';
|
||||||
@Input() savingsRate = 0;
|
@Input() savingsRate = 0;
|
||||||
|
|
||||||
|
@ -40,7 +40,11 @@
|
|||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[isCurrency]="true"
|
[isCurrency]="true"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[value]="isLoading ? undefined : performance?.currentNetPerformance"
|
[value]="
|
||||||
|
isLoading
|
||||||
|
? undefined
|
||||||
|
: performance?.currentNetPerformanceWithCurrencyEffect
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@ -49,7 +53,9 @@
|
|||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[value]="
|
[value]="
|
||||||
isLoading ? undefined : performance?.currentNetPerformancePercent
|
isLoading
|
||||||
|
? undefined
|
||||||
|
: performance?.currentNetPerformancePercentWithCurrencyEffect
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
getLocale,
|
||||||
getNumberFormatDecimal,
|
getNumberFormatDecimal,
|
||||||
getNumberFormatGroup
|
getNumberFormatGroup
|
||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
@ -31,7 +32,7 @@ export class PortfolioPerformanceComponent implements OnChanges, OnInit {
|
|||||||
@Input() isAllTimeHigh: boolean;
|
@Input() isAllTimeHigh: boolean;
|
||||||
@Input() isAllTimeLow: boolean;
|
@Input() isAllTimeLow: boolean;
|
||||||
@Input() isLoading: boolean;
|
@Input() isLoading: boolean;
|
||||||
@Input() locale: string;
|
@Input() locale = getLocale();
|
||||||
@Input() performance: PortfolioPerformance;
|
@Input() performance: PortfolioPerformance;
|
||||||
@Input() showDetails: boolean;
|
@Input() showDetails: boolean;
|
||||||
@Input() unit: string;
|
@Input() unit: string;
|
||||||
@ -62,7 +63,8 @@ export class PortfolioPerformanceComponent implements OnChanges, OnInit {
|
|||||||
} else if (this.showDetails === false) {
|
} else if (this.showDetails === false) {
|
||||||
new CountUp(
|
new CountUp(
|
||||||
'value',
|
'value',
|
||||||
this.performance?.currentNetPerformancePercent * 100,
|
this.performance?.currentNetPerformancePercentWithCurrencyEffect *
|
||||||
|
100,
|
||||||
{
|
{
|
||||||
decimal: getNumberFormatDecimal(this.locale),
|
decimal: getNumberFormatDecimal(this.locale),
|
||||||
decimalPlaces: 2,
|
decimalPlaces: 2,
|
||||||
|
@ -64,7 +64,11 @@
|
|||||||
[isCurrency]="true"
|
[isCurrency]="true"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[unit]="baseCurrency"
|
[unit]="baseCurrency"
|
||||||
[value]="isLoading ? undefined : summary?.currentGrossPerformance"
|
[value]="
|
||||||
|
isLoading
|
||||||
|
? undefined
|
||||||
|
: summary?.currentGrossPerformanceWithCurrencyEffect
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -85,7 +89,9 @@
|
|||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[value]="
|
[value]="
|
||||||
isLoading ? undefined : summary?.currentGrossPerformancePercent
|
isLoading
|
||||||
|
? undefined
|
||||||
|
: summary?.currentGrossPerformancePercentWithCurrencyEffect
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -114,7 +120,11 @@
|
|||||||
[isCurrency]="true"
|
[isCurrency]="true"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[unit]="baseCurrency"
|
[unit]="baseCurrency"
|
||||||
[value]="isLoading ? undefined : summary?.currentNetPerformance"
|
[value]="
|
||||||
|
isLoading
|
||||||
|
? undefined
|
||||||
|
: summary?.currentNetPerformanceWithCurrencyEffect
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -134,7 +144,11 @@
|
|||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[value]="isLoading ? undefined : summary?.currentNetPerformancePercent"
|
[value]="
|
||||||
|
isLoading
|
||||||
|
? undefined
|
||||||
|
: summary?.currentNetPerformancePercentWithCurrencyEffect
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -283,7 +297,11 @@
|
|||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[value]="isLoading ? undefined : summary?.annualizedPerformancePercent"
|
[value]="
|
||||||
|
isLoading
|
||||||
|
? undefined
|
||||||
|
: summary?.annualizedPerformancePercentWithCurrencyEffect
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { getDateFnsLocale } from '@ghostfolio/common/helper';
|
import { getDateFnsLocale, getLocale } from '@ghostfolio/common/helper';
|
||||||
import { PortfolioSummary } from '@ghostfolio/common/interfaces';
|
import { PortfolioSummary } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -23,7 +23,7 @@ export class PortfolioSummaryComponent implements OnChanges, OnInit {
|
|||||||
@Input() hasPermissionToUpdateUserSettings: boolean;
|
@Input() hasPermissionToUpdateUserSettings: boolean;
|
||||||
@Input() isLoading: boolean;
|
@Input() isLoading: boolean;
|
||||||
@Input() language: string;
|
@Input() language: string;
|
||||||
@Input() locale: string;
|
@Input() locale = getLocale();
|
||||||
@Input() summary: PortfolioSummary;
|
@Input() summary: PortfolioSummary;
|
||||||
|
|
||||||
@Output() emergencyFundChanged = new EventEmitter<number>();
|
@Output() emergencyFundChanged = new EventEmitter<number>();
|
||||||
|
@ -50,15 +50,13 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
|
|||||||
public dividendInBaseCurrency: number;
|
public dividendInBaseCurrency: number;
|
||||||
public feeInBaseCurrency: number;
|
public feeInBaseCurrency: number;
|
||||||
public firstBuyDate: string;
|
public firstBuyDate: string;
|
||||||
public grossPerformance: number;
|
|
||||||
public grossPerformancePercent: number;
|
|
||||||
public historicalDataItems: LineChartItem[];
|
public historicalDataItems: LineChartItem[];
|
||||||
public investment: number;
|
public investment: number;
|
||||||
public marketPrice: number;
|
public marketPrice: number;
|
||||||
public maxPrice: number;
|
public maxPrice: number;
|
||||||
public minPrice: number;
|
public minPrice: number;
|
||||||
public netPerformance: number;
|
public netPerformancePercentWithCurrencyEffect: number;
|
||||||
public netPerformancePercent: number;
|
public netPerformanceWithCurrencyEffect: number;
|
||||||
public quantity: number;
|
public quantity: number;
|
||||||
public quantityPrecision = 2;
|
public quantityPrecision = 2;
|
||||||
public reportDataGlitchMail: string;
|
public reportDataGlitchMail: string;
|
||||||
@ -99,15 +97,13 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
|
|||||||
dividendInBaseCurrency,
|
dividendInBaseCurrency,
|
||||||
feeInBaseCurrency,
|
feeInBaseCurrency,
|
||||||
firstBuyDate,
|
firstBuyDate,
|
||||||
grossPerformance,
|
|
||||||
grossPerformancePercent,
|
|
||||||
historicalData,
|
historicalData,
|
||||||
investment,
|
investment,
|
||||||
marketPrice,
|
marketPrice,
|
||||||
maxPrice,
|
maxPrice,
|
||||||
minPrice,
|
minPrice,
|
||||||
netPerformance,
|
netPerformancePercentWithCurrencyEffect,
|
||||||
netPerformancePercent,
|
netPerformanceWithCurrencyEffect,
|
||||||
orders,
|
orders,
|
||||||
quantity,
|
quantity,
|
||||||
SymbolProfile,
|
SymbolProfile,
|
||||||
@ -125,8 +121,6 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
|
|||||||
this.dividendInBaseCurrency = dividendInBaseCurrency;
|
this.dividendInBaseCurrency = dividendInBaseCurrency;
|
||||||
this.feeInBaseCurrency = feeInBaseCurrency;
|
this.feeInBaseCurrency = feeInBaseCurrency;
|
||||||
this.firstBuyDate = firstBuyDate;
|
this.firstBuyDate = firstBuyDate;
|
||||||
this.grossPerformance = grossPerformance;
|
|
||||||
this.grossPerformancePercent = grossPerformancePercent;
|
|
||||||
this.historicalDataItems = historicalData.map(
|
this.historicalDataItems = historicalData.map(
|
||||||
(historicalDataItem) => {
|
(historicalDataItem) => {
|
||||||
this.benchmarkDataItems.push({
|
this.benchmarkDataItems.push({
|
||||||
@ -144,8 +138,10 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
|
|||||||
this.marketPrice = marketPrice;
|
this.marketPrice = marketPrice;
|
||||||
this.maxPrice = maxPrice;
|
this.maxPrice = maxPrice;
|
||||||
this.minPrice = minPrice;
|
this.minPrice = minPrice;
|
||||||
this.netPerformance = netPerformance;
|
this.netPerformancePercentWithCurrencyEffect =
|
||||||
this.netPerformancePercent = netPerformancePercent;
|
netPerformancePercentWithCurrencyEffect;
|
||||||
|
this.netPerformanceWithCurrencyEffect =
|
||||||
|
netPerformanceWithCurrencyEffect;
|
||||||
this.quantity = quantity;
|
this.quantity = quantity;
|
||||||
this.reportDataGlitchMail = `mailto:hi@ghostfol.io?Subject=Ghostfolio Data Glitch Report&body=Hello%0D%0DI would like to report a data glitch for%0D%0DSymbol: ${SymbolProfile?.symbol}%0DData Source: ${SymbolProfile?.dataSource}%0D%0DAdditional notes:%0D%0DCan you please take a look?%0D%0DKind regards`;
|
this.reportDataGlitchMail = `mailto:hi@ghostfol.io?Subject=Ghostfolio Data Glitch Report&body=Hello%0D%0DI would like to report a data glitch for%0D%0DSymbol: ${SymbolProfile?.symbol}%0DData Source: ${SymbolProfile?.dataSource}%0D%0DAdditional notes:%0D%0DCan you please take a look?%0D%0DKind regards`;
|
||||||
this.sectors = {};
|
this.sectors = {};
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
[isCurrency]="true"
|
[isCurrency]="true"
|
||||||
[locale]="data.locale"
|
[locale]="data.locale"
|
||||||
[unit]="data.baseCurrency"
|
[unit]="data.baseCurrency"
|
||||||
[value]="netPerformance"
|
[value]="netPerformanceWithCurrencyEffect"
|
||||||
>Change</gf-value
|
>Change</gf-value
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@ -55,7 +55,7 @@
|
|||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="data.locale"
|
[locale]="data.locale"
|
||||||
[value]="netPerformancePercent"
|
[value]="netPerformancePercentWithCurrencyEffect"
|
||||||
>Performance</gf-value
|
>Performance</gf-value
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@ -87,7 +87,11 @@
|
|||||||
size="medium"
|
size="medium"
|
||||||
[isCurrency]="true"
|
[isCurrency]="true"
|
||||||
[locale]="data.locale"
|
[locale]="data.locale"
|
||||||
[ngClass]="{ 'text-danger': minPrice?.toFixed(2) === marketPrice?.toFixed(2) && maxPrice?.toFixed(2) !== minPrice?.toFixed(2) }"
|
[ngClass]="{
|
||||||
|
'text-danger':
|
||||||
|
minPrice?.toFixed(2) === marketPrice?.toFixed(2) &&
|
||||||
|
maxPrice?.toFixed(2) !== minPrice?.toFixed(2)
|
||||||
|
}"
|
||||||
[unit]="SymbolProfile?.currency"
|
[unit]="SymbolProfile?.currency"
|
||||||
[value]="minPrice"
|
[value]="minPrice"
|
||||||
>Minimum Price</gf-value
|
>Minimum Price</gf-value
|
||||||
@ -99,7 +103,11 @@
|
|||||||
size="medium"
|
size="medium"
|
||||||
[isCurrency]="true"
|
[isCurrency]="true"
|
||||||
[locale]="data.locale"
|
[locale]="data.locale"
|
||||||
[ngClass]="{ 'text-success': maxPrice?.toFixed(2) === marketPrice?.toFixed(2) && maxPrice?.toFixed(2) !== minPrice?.toFixed(2) }"
|
[ngClass]="{
|
||||||
|
'text-success':
|
||||||
|
maxPrice?.toFixed(2) === marketPrice?.toFixed(2) &&
|
||||||
|
maxPrice?.toFixed(2) !== minPrice?.toFixed(2)
|
||||||
|
}"
|
||||||
[unit]="SymbolProfile?.currency"
|
[unit]="SymbolProfile?.currency"
|
||||||
[value]="maxPrice"
|
[value]="maxPrice"
|
||||||
>Maximum Price</gf-value
|
>Maximum Price</gf-value
|
||||||
@ -184,53 +192,61 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngIf="SymbolProfile?.countries?.length > 0 || SymbolProfile?.sectors?.length > 0"
|
*ngIf="
|
||||||
|
SymbolProfile?.countries?.length > 0 ||
|
||||||
|
SymbolProfile?.sectors?.length > 0
|
||||||
|
"
|
||||||
>
|
>
|
||||||
@if(SymbolProfile?.countries?.length === 1 &&
|
@if (
|
||||||
SymbolProfile?.sectors?.length === 1) {
|
SymbolProfile?.countries?.length === 1 &&
|
||||||
<div *ngIf="SymbolProfile?.sectors?.length === 1" class="col-6 mb-3">
|
SymbolProfile?.sectors?.length === 1
|
||||||
<gf-value
|
) {
|
||||||
i18n
|
<div *ngIf="SymbolProfile?.sectors?.length === 1" class="col-6 mb-3">
|
||||||
size="medium"
|
<gf-value
|
||||||
[locale]="data.locale"
|
i18n
|
||||||
[value]="SymbolProfile.sectors[0].name"
|
size="medium"
|
||||||
>Sector</gf-value
|
[locale]="data.locale"
|
||||||
|
[value]="SymbolProfile.sectors[0].name"
|
||||||
|
>Sector</gf-value
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
*ngIf="SymbolProfile?.countries?.length === 1"
|
||||||
|
class="col-6 mb-3"
|
||||||
>
|
>
|
||||||
</div>
|
<gf-value
|
||||||
<div *ngIf="SymbolProfile?.countries?.length === 1" class="col-6 mb-3">
|
i18n
|
||||||
<gf-value
|
size="medium"
|
||||||
i18n
|
[locale]="data.locale"
|
||||||
size="medium"
|
[value]="SymbolProfile.countries[0].name"
|
||||||
[locale]="data.locale"
|
>Country</gf-value
|
||||||
[value]="SymbolProfile.countries[0].name"
|
>
|
||||||
>Country</gf-value
|
</div>
|
||||||
>
|
|
||||||
</div>
|
|
||||||
} @else {
|
} @else {
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="h5" i18n>Sectors</div>
|
<div class="h5" i18n>Sectors</div>
|
||||||
<gf-portfolio-proportion-chart
|
<gf-portfolio-proportion-chart
|
||||||
[baseCurrency]="data.baseCurrency"
|
[baseCurrency]="data.baseCurrency"
|
||||||
[colorScheme]="data.colorScheme"
|
[colorScheme]="data.colorScheme"
|
||||||
[isInPercent]="true"
|
[isInPercent]="true"
|
||||||
[keys]="['name']"
|
[keys]="['name']"
|
||||||
[locale]="data.locale"
|
[locale]="data.locale"
|
||||||
[maxItems]="10"
|
[maxItems]="10"
|
||||||
[positions]="sectors"
|
[positions]="sectors"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<div class="h5" i18n>Countries</div>
|
<div class="h5" i18n>Countries</div>
|
||||||
<gf-portfolio-proportion-chart
|
<gf-portfolio-proportion-chart
|
||||||
[baseCurrency]="data.baseCurrency"
|
[baseCurrency]="data.baseCurrency"
|
||||||
[colorScheme]="data.colorScheme"
|
[colorScheme]="data.colorScheme"
|
||||||
[isInPercent]="true"
|
[isInPercent]="true"
|
||||||
[keys]="['name']"
|
[keys]="['name']"
|
||||||
[locale]="data.locale"
|
[locale]="data.locale"
|
||||||
[maxItems]="10"
|
[maxItems]="10"
|
||||||
[positions]="countries"
|
[positions]="countries"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div *ngIf="dataProviderInfo" class="col-md-12 mb-3 text-center">
|
<div *ngIf="dataProviderInfo" class="col-md-12 mb-3 text-center">
|
||||||
@ -257,7 +273,9 @@
|
|||||||
[dataSource]="dataSource"
|
[dataSource]="dataSource"
|
||||||
[deviceType]="data.deviceType"
|
[deviceType]="data.deviceType"
|
||||||
[hasPermissionToCreateActivity]="false"
|
[hasPermissionToCreateActivity]="false"
|
||||||
[hasPermissionToExportActivities]="!data.hasImpersonationId && !user.settings.isRestrictedView"
|
[hasPermissionToExportActivities]="
|
||||||
|
!data.hasImpersonationId && !user.settings.isRestrictedView
|
||||||
|
"
|
||||||
[hasPermissionToFilter]="false"
|
[hasPermissionToFilter]="false"
|
||||||
[hasPermissionToOpenDetails]="false"
|
[hasPermissionToOpenDetails]="false"
|
||||||
[locale]="data.locale"
|
[locale]="data.locale"
|
||||||
@ -294,15 +312,17 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="h5" i18n>Tags</div>
|
<div class="h5" i18n>Tags</div>
|
||||||
<mat-chip-listbox>
|
<mat-chip-listbox>
|
||||||
<mat-chip-option *ngFor="let tag of tags" disabled
|
<mat-chip-option *ngFor="let tag of tags" disabled>{{
|
||||||
>{{ tag.name }}</mat-chip-option
|
tag.name
|
||||||
>
|
}}</mat-chip-option>
|
||||||
</mat-chip-listbox>
|
</mat-chip-listbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
*ngIf="activities?.length > 0 && data.hasPermissionToReportDataGlitch === true"
|
*ngIf="
|
||||||
|
activities?.length > 0 && data.hasPermissionToReportDataGlitch === true
|
||||||
|
"
|
||||||
class="row"
|
class="row"
|
||||||
>
|
>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
[isLoading]="isLoading"
|
[isLoading]="isLoading"
|
||||||
[marketState]="position?.marketState"
|
[marketState]="position?.marketState"
|
||||||
[range]="range"
|
[range]="range"
|
||||||
[value]="position?.netPerformancePercentage"
|
[value]="position?.netPerformancePercentageWithCurrencyEffect"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="isLoading" class="flex-grow-1">
|
<div *ngIf="isLoading" class="flex-grow-1">
|
||||||
@ -49,13 +49,13 @@
|
|||||||
[isCurrency]="true"
|
[isCurrency]="true"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[unit]="baseCurrency"
|
[unit]="baseCurrency"
|
||||||
[value]="position?.netPerformance"
|
[value]="position?.netPerformanceWithCurrencyEffect"
|
||||||
/>
|
/>
|
||||||
<gf-value
|
<gf-value
|
||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[value]="position?.netPerformancePercentage"
|
[value]="position?.netPerformancePercentageWithCurrencyEffect"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
||||||
|
import { getLocale } from '@ghostfolio/common/helper';
|
||||||
import { Position } from '@ghostfolio/common/interfaces';
|
import { Position } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -20,7 +21,7 @@ export class PositionComponent implements OnDestroy, OnInit {
|
|||||||
@Input() baseCurrency: string;
|
@Input() baseCurrency: string;
|
||||||
@Input() deviceType: string;
|
@Input() deviceType: string;
|
||||||
@Input() isLoading: boolean;
|
@Input() isLoading: boolean;
|
||||||
@Input() locale: string;
|
@Input() locale = getLocale();
|
||||||
@Input() position: Position;
|
@Input() position: Position;
|
||||||
@Input() range: string;
|
@Input() range: string;
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { getLocale } from '@ghostfolio/common/helper';
|
||||||
import { Position } from '@ghostfolio/common/interfaces';
|
import { Position } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -18,7 +19,7 @@ export class PositionsComponent implements OnChanges, OnInit {
|
|||||||
@Input() baseCurrency: string;
|
@Input() baseCurrency: string;
|
||||||
@Input() deviceType: string;
|
@Input() deviceType: string;
|
||||||
@Input() hasPermissionToCreateOrder: boolean;
|
@Input() hasPermissionToCreateOrder: boolean;
|
||||||
@Input() locale: string;
|
@Input() locale = getLocale();
|
||||||
@Input() positions: Position[];
|
@Input() positions: Position[];
|
||||||
@Input() range: string;
|
@Input() range: string;
|
||||||
|
|
||||||
|
@ -28,30 +28,32 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (accessForm.controls['type'].value === 'PRIVATE') {
|
@if (accessForm.controls['type'].value === 'PRIVATE') {
|
||||||
<div>
|
<div>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Permission</mat-label>
|
<mat-label i18n>Permission</mat-label>
|
||||||
<mat-select formControlName="permissions">
|
<mat-select formControlName="permissions">
|
||||||
<mat-option i18n value="READ_RESTRICTED">Restricted view</mat-option>
|
<mat-option i18n value="READ_RESTRICTED"
|
||||||
@if(data?.user?.settings?.isExperimentalFeatures) {
|
>Restricted view</mat-option
|
||||||
<mat-option i18n value="READ">View</mat-option>
|
>
|
||||||
}
|
@if (data?.user?.settings?.isExperimentalFeatures) {
|
||||||
</mat-select>
|
<mat-option i18n value="READ">View</mat-option>
|
||||||
</mat-form-field>
|
}
|
||||||
</div>
|
</mat-select>
|
||||||
<div>
|
</mat-form-field>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
</div>
|
||||||
<mat-label>
|
<div>
|
||||||
Ghostfolio <ng-container i18n>User ID</ng-container>
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
</mat-label>
|
<mat-label>
|
||||||
<input
|
Ghostfolio <ng-container i18n>User ID</ng-container>
|
||||||
formControlName="userId"
|
</mat-label>
|
||||||
matInput
|
<input
|
||||||
type="text"
|
formControlName="userId"
|
||||||
(keydown.enter)="$event.stopPropagation()"
|
matInput
|
||||||
/>
|
type="text"
|
||||||
</mat-form-field>
|
(keydown.enter)="$event.stopPropagation()"
|
||||||
</div>
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="justify-content-end" mat-dialog-actions>
|
<div class="justify-content-end" mat-dialog-actions>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="align-items-center d-flex flex-column">
|
<div class="align-items-center d-flex flex-column">
|
||||||
<gf-membership-card
|
<gf-membership-card
|
||||||
[expiresAt]="user?.subscription?.expiresAt | date: defaultDateFormat"
|
[expiresAt]="user?.subscription?.expiresAt | date: defaultDateFormat"
|
||||||
[name]="user?.subscription?.type"
|
[name]="user?.subscription?.type"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@ -11,14 +11,19 @@
|
|||||||
class="d-flex flex-column mt-5"
|
class="d-flex flex-column mt-5"
|
||||||
>
|
>
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngIf="hasPermissionForSubscription && hasPermissionToUpdateUserSettings"
|
*ngIf="
|
||||||
|
hasPermissionForSubscription && hasPermissionToUpdateUserSettings
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<button color="primary" mat-flat-button (click)="onCheckout()">
|
<button color="primary" mat-flat-button (click)="onCheckout()">
|
||||||
<ng-container *ngIf="user.subscription.offer === 'default'" i18n
|
<ng-container *ngIf="user.subscription.offer === 'default'" i18n
|
||||||
>Upgrade Plan</ng-container
|
>Upgrade Plan</ng-container
|
||||||
>
|
>
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngIf="user.subscription.offer === 'renewal' || user.subscription.offer === 'renewal-early-bird'"
|
*ngIf="
|
||||||
|
user.subscription.offer === 'renewal' ||
|
||||||
|
user.subscription.offer === 'renewal-early-bird'
|
||||||
|
"
|
||||||
i18n
|
i18n
|
||||||
>Renew Plan</ng-container
|
>Renew Plan</ng-container
|
||||||
>
|
>
|
||||||
@ -27,7 +32,8 @@
|
|||||||
<ng-container *ngIf="coupon"
|
<ng-container *ngIf="coupon"
|
||||||
><del class="text-muted"
|
><del class="text-muted"
|
||||||
>{{ baseCurrency }} {{ price }}</del
|
>{{ baseCurrency }} {{ price }}</del
|
||||||
> {{ baseCurrency }} {{ price - coupon
|
> {{ baseCurrency }} {{
|
||||||
|
price - coupon
|
||||||
}}</ng-container
|
}}</ng-container
|
||||||
>
|
>
|
||||||
<ng-container *ngIf="!coupon"
|
<ng-container *ngIf="!coupon"
|
||||||
|
@ -32,7 +32,9 @@
|
|||||||
name="baseCurrency"
|
name="baseCurrency"
|
||||||
[disabled]="!hasPermissionToUpdateUserSettings"
|
[disabled]="!hasPermissionToUpdateUserSettings"
|
||||||
[value]="user.settings.baseCurrency"
|
[value]="user.settings.baseCurrency"
|
||||||
(selectionChange)="onChangeUserSetting('baseCurrency', $event.value)"
|
(selectionChange)="
|
||||||
|
onChangeUserSetting('baseCurrency', $event.value)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<mat-option
|
<mat-option
|
||||||
*ngFor="let currency of currencies"
|
*ngFor="let currency of currencies"
|
||||||
@ -53,7 +55,9 @@
|
|||||||
>
|
>
|
||||||
If a translation is missing, kindly support us in extending it
|
If a translation is missing, kindly support us in extending it
|
||||||
<a
|
<a
|
||||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/apps/client/src/locales/messages.{{language}}.xlf"
|
href="https://github.com/ghostfolio/ghostfolio/blob/main/apps/client/src/locales/messages.{{
|
||||||
|
language
|
||||||
|
}}.xlf"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>here</a
|
>here</a
|
||||||
>.
|
>.
|
||||||
@ -65,7 +69,9 @@
|
|||||||
name="language"
|
name="language"
|
||||||
[disabled]="!hasPermissionToUpdateUserSettings"
|
[disabled]="!hasPermissionToUpdateUserSettings"
|
||||||
[value]="language"
|
[value]="language"
|
||||||
(selectionChange)="onChangeUserSetting('language', $event.value)"
|
(selectionChange)="
|
||||||
|
onChangeUserSetting('language', $event.value)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<mat-option [value]="null" />
|
<mat-option [value]="null" />
|
||||||
<mat-option value="de">Deutsch</mat-option>
|
<mat-option value="de">Deutsch</mat-option>
|
||||||
@ -115,12 +121,14 @@
|
|||||||
name="locale"
|
name="locale"
|
||||||
[disabled]="!hasPermissionToUpdateUserSettings"
|
[disabled]="!hasPermissionToUpdateUserSettings"
|
||||||
[value]="user.settings.locale"
|
[value]="user.settings.locale"
|
||||||
(selectionChange)="onChangeUserSetting('locale', $event.value)"
|
(selectionChange)="
|
||||||
|
onChangeUserSetting('locale', $event.value)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<mat-option [value]="null" />
|
<mat-option [value]="null" />
|
||||||
<mat-option *ngFor="let locale of locales" [value]="locale"
|
<mat-option *ngFor="let locale of locales" [value]="locale">{{
|
||||||
>{{ locale }}</mat-option
|
locale
|
||||||
>
|
}}</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
@ -137,7 +145,9 @@
|
|||||||
[disabled]="!hasPermissionToUpdateUserSettings"
|
[disabled]="!hasPermissionToUpdateUserSettings"
|
||||||
[placeholder]="appearancePlaceholder"
|
[placeholder]="appearancePlaceholder"
|
||||||
[value]="user?.settings?.colorScheme"
|
[value]="user?.settings?.colorScheme"
|
||||||
(selectionChange)="onChangeUserSetting('colorScheme', $event.value)"
|
(selectionChange)="
|
||||||
|
onChangeUserSetting('colorScheme', $event.value)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<mat-option i18n [value]="null">Auto</mat-option>
|
<mat-option i18n [value]="null">Auto</mat-option>
|
||||||
<mat-option i18n value="LIGHT">Light</mat-option>
|
<mat-option i18n value="LIGHT">Light</mat-option>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { getNumberFormatGroup } from '@ghostfolio/common/helper';
|
import { getLocale, getNumberFormatGroup } from '@ghostfolio/common/helper';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
@ -21,7 +21,7 @@ export class WorldMapChartComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
@Input() countries: { [code: string]: { name?: string; value: number } };
|
@Input() countries: { [code: string]: { name?: string; value: number } };
|
||||||
@Input() format: string;
|
@Input() format: string;
|
||||||
@Input() isInPercent = false;
|
@Input() isInPercent = false;
|
||||||
@Input() locale: string;
|
@Input() locale = getLocale();
|
||||||
|
|
||||||
public isLoading = true;
|
public isLoading = true;
|
||||||
public svgMapElement;
|
public svgMapElement;
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
>
|
>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
[name]="tab.iconName"
|
[name]="tab.iconName"
|
||||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||||
/>
|
/>
|
||||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -11,26 +11,28 @@
|
|||||||
</h1>
|
</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@for (ossFriend of ossFriends; track ossFriend) {
|
@for (ossFriend of ossFriends; track ossFriend) {
|
||||||
<div class="col-xs-12 col-md-4 mb-3">
|
<div class="col-xs-12 col-md-4 mb-3">
|
||||||
<a target="_blank" [href]="ossFriend.href">
|
<a target="_blank" [href]="ossFriend.href">
|
||||||
<mat-card appearance="outlined" class="d-flex flex-column h-100">
|
<mat-card appearance="outlined" class="d-flex flex-column h-100">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title class="h4">{{ ossFriend.name }}</mat-card-title>
|
<mat-card-title class="h4">{{
|
||||||
</mat-card-header>
|
ossFriend.name
|
||||||
<mat-card-content class="flex-grow-1">
|
}}</mat-card-title>
|
||||||
<p>{{ ossFriend.description }}</p>
|
</mat-card-header>
|
||||||
</mat-card-content>
|
<mat-card-content class="flex-grow-1">
|
||||||
<mat-card-actions class="justify-content-end">
|
<p>{{ ossFriend.description }}</p>
|
||||||
<a mat-button target="_blank" [href]="ossFriend.href">
|
</mat-card-content>
|
||||||
<span
|
<mat-card-actions class="justify-content-end">
|
||||||
><ng-container i18n>Visit</ng-container> {{ ossFriend.name
|
<a mat-button target="_blank" [href]="ossFriend.href">
|
||||||
}}</span
|
<span
|
||||||
><ion-icon class="ml-1" name="arrow-forward-outline" />
|
><ng-container i18n>Visit</ng-container>
|
||||||
</a>
|
{{ ossFriend.name }}</span
|
||||||
</mat-card-actions>
|
><ion-icon class="ml-1" name="arrow-forward-outline" />
|
||||||
</mat-card>
|
</a>
|
||||||
</a>
|
</mat-card-actions>
|
||||||
</div>
|
</mat-card>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -147,15 +147,15 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@if (hasPermissionForSubscription) {
|
@if (hasPermissionForSubscription) {
|
||||||
<div class="col-md-6 col-xs-12 my-2">
|
<div class="col-md-6 col-xs-12 my-2">
|
||||||
<a
|
<a
|
||||||
class="py-4 w-100"
|
class="py-4 w-100"
|
||||||
color="primary"
|
color="primary"
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[routerLink]="['/blog']"
|
[routerLink]="['/blog']"
|
||||||
>Blog</a
|
>Blog</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,7 +8,11 @@
|
|||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
[deviceType]="deviceType"
|
[deviceType]="deviceType"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[showActions]="!hasImpersonationId && hasPermissionToUpdateAccount && !user.settings.isRestrictedView"
|
[showActions]="
|
||||||
|
!hasImpersonationId &&
|
||||||
|
hasPermissionToUpdateAccount &&
|
||||||
|
!user.settings.isRestrictedView
|
||||||
|
"
|
||||||
[totalBalanceInBaseCurrency]="totalBalanceInBaseCurrency"
|
[totalBalanceInBaseCurrency]="totalBalanceInBaseCurrency"
|
||||||
[totalValueInBaseCurrency]="totalValueInBaseCurrency"
|
[totalValueInBaseCurrency]="totalValueInBaseCurrency"
|
||||||
[transactionCount]="transactionCount"
|
[transactionCount]="transactionCount"
|
||||||
@ -21,7 +25,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
*ngIf="!hasImpersonationId && hasPermissionToCreateAccount && !user.settings.isRestrictedView"
|
*ngIf="
|
||||||
|
!hasImpersonationId &&
|
||||||
|
hasPermissionToCreateAccount &&
|
||||||
|
!user.settings.isRestrictedView
|
||||||
|
"
|
||||||
class="fab-container"
|
class="fab-container"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
|
@ -5,9 +5,9 @@
|
|||||||
(ngSubmit)="onSubmit()"
|
(ngSubmit)="onSubmit()"
|
||||||
>
|
>
|
||||||
@if (data.account.id) {
|
@if (data.account.id) {
|
||||||
<h1 i18n mat-dialog-title>Update account</h1>
|
<h1 i18n mat-dialog-title>Update account</h1>
|
||||||
} @else {
|
} @else {
|
||||||
<h1 i18n mat-dialog-title>Add account</h1>
|
<h1 i18n mat-dialog-title>Add account</h1>
|
||||||
}
|
}
|
||||||
<div class="flex-grow-1 py-3" mat-dialog-content>
|
<div class="flex-grow-1 py-3" mat-dialog-content>
|
||||||
<div>
|
<div>
|
||||||
@ -38,9 +38,9 @@
|
|||||||
type="number"
|
type="number"
|
||||||
(keydown.enter)="$event.stopPropagation()"
|
(keydown.enter)="$event.stopPropagation()"
|
||||||
/>
|
/>
|
||||||
<span class="ml-2" matTextSuffix
|
<span class="ml-2" matTextSuffix>{{
|
||||||
>{{ accountForm.controls['currency']?.value?.value }}</span
|
accountForm.controls['currency']?.value?.value
|
||||||
>
|
}}</span>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div [ngClass]="{ 'd-none': platforms?.length < 1 }">
|
<div [ngClass]="{ 'd-none': platforms?.length < 1 }">
|
||||||
@ -55,18 +55,20 @@
|
|||||||
(keydown.enter)="$event.stopPropagation()"
|
(keydown.enter)="$event.stopPropagation()"
|
||||||
/>
|
/>
|
||||||
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
|
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
|
||||||
@for (platformEntry of filteredPlatforms | async; track platformEntry)
|
@for (
|
||||||
{
|
platformEntry of filteredPlatforms | async;
|
||||||
<mat-option [value]="platformEntry">
|
track platformEntry
|
||||||
<span class="d-flex">
|
) {
|
||||||
<gf-symbol-icon
|
<mat-option [value]="platformEntry">
|
||||||
class="mr-1"
|
<span class="d-flex">
|
||||||
[tooltip]="platformEntry.name"
|
<gf-symbol-icon
|
||||||
[url]="platformEntry.url"
|
class="mr-1"
|
||||||
/>
|
[tooltip]="platformEntry.name"
|
||||||
<span>{{ platformEntry.name }}</span>
|
[url]="platformEntry.url"
|
||||||
</span>
|
/>
|
||||||
</mat-option>
|
<span>{{ platformEntry.name }}</span>
|
||||||
|
</span>
|
||||||
|
</mat-option>
|
||||||
}
|
}
|
||||||
</mat-autocomplete>
|
</mat-autocomplete>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
@ -89,12 +91,12 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@if (data.account.id) {
|
@if (data.account.id) {
|
||||||
<div>
|
<div>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Account ID</mat-label>
|
<mat-label i18n>Account ID</mat-label>
|
||||||
<input formControlName="accountId" matInput />
|
<input formControlName="accountId" matInput />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="justify-content-end" mat-dialog-actions>
|
<div class="justify-content-end" mat-dialog-actions>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
>
|
>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
[name]="tab.iconName"
|
[name]="tab.iconName"
|
||||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||||
/>
|
/>
|
||||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -9,30 +9,30 @@
|
|||||||
>
|
>
|
||||||
</h1>
|
</h1>
|
||||||
@if (hasPermissionForSubscription) {
|
@if (hasPermissionForSubscription) {
|
||||||
<mat-card appearance="outlined" class="mb-3">
|
<mat-card appearance="outlined" class="mb-3">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<div class="container p-0">
|
<div class="container p-0">
|
||||||
<div class="flex-nowrap no-gutters row">
|
<div class="flex-nowrap no-gutters row">
|
||||||
<a
|
<a
|
||||||
class="d-flex overflow-hidden w-100"
|
class="d-flex overflow-hidden w-100"
|
||||||
href="../en/blog/2023/11/black-week-2023"
|
href="../en/blog/2023/11/black-week-2023"
|
||||||
>
|
>
|
||||||
<div class="flex-grow-1 overflow-hidden">
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
<div class="h6 m-0 text-truncate">Black Week 2023</div>
|
<div class="h6 m-0 text-truncate">Black Week 2023</div>
|
||||||
<div class="d-flex text-muted">2023-11-19</div>
|
<div class="d-flex text-muted">2023-11-19</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="align-items-center d-flex">
|
<div class="align-items-center d-flex">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="chevron text-muted"
|
class="chevron text-muted"
|
||||||
name="chevron-forward-outline"
|
name="chevron-forward-outline"
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</mat-card-content>
|
||||||
</mat-card-content>
|
</mat-card>
|
||||||
</mat-card>
|
|
||||||
}
|
}
|
||||||
<mat-card appearance="outlined" class="mb-3">
|
<mat-card appearance="outlined" class="mb-3">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
@ -294,30 +294,30 @@
|
|||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
@if (hasPermissionForSubscription) {
|
@if (hasPermissionForSubscription) {
|
||||||
<mat-card appearance="outlined" class="mb-3">
|
<mat-card appearance="outlined" class="mb-3">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<div class="container p-0">
|
<div class="container p-0">
|
||||||
<div class="flex-nowrap no-gutters row">
|
<div class="flex-nowrap no-gutters row">
|
||||||
<a
|
<a
|
||||||
class="d-flex overflow-hidden w-100"
|
class="d-flex overflow-hidden w-100"
|
||||||
href="../en/blog/2022/11/black-friday-2022"
|
href="../en/blog/2022/11/black-friday-2022"
|
||||||
>
|
>
|
||||||
<div class="flex-grow-1 overflow-hidden">
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
<div class="h6 m-0 text-truncate">Black Friday 2022</div>
|
<div class="h6 m-0 text-truncate">Black Friday 2022</div>
|
||||||
<div class="d-flex text-muted">2022-11-13</div>
|
<div class="d-flex text-muted">2022-11-13</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="align-items-center d-flex">
|
<div class="align-items-center d-flex">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="chevron text-muted"
|
class="chevron text-muted"
|
||||||
name="chevron-forward-outline"
|
name="chevron-forward-outline"
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</mat-card-content>
|
||||||
</mat-card-content>
|
</mat-card>
|
||||||
</mat-card>
|
|
||||||
}
|
}
|
||||||
<mat-card appearance="outlined" class="mb-3">
|
<mat-card appearance="outlined" class="mb-3">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
>
|
>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
[name]="tab.iconName"
|
[name]="tab.iconName"
|
||||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||||
/>
|
/>
|
||||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -140,7 +140,7 @@
|
|||||||
<h4 class="align-items-center d-flex">
|
<h4 class="align-items-center d-flex">
|
||||||
<span i18n>Portfolio Calculations</span>
|
<span i18n>Portfolio Calculations</span>
|
||||||
@if (hasPermissionForSubscription) {
|
@if (hasPermissionForSubscription) {
|
||||||
<gf-premium-indicator class="ml-1" />
|
<gf-premium-indicator class="ml-1" />
|
||||||
}
|
}
|
||||||
</h4>
|
</h4>
|
||||||
<p class="m-0">
|
<p class="m-0">
|
||||||
@ -159,7 +159,7 @@
|
|||||||
<h4 class="align-items-center d-flex">
|
<h4 class="align-items-center d-flex">
|
||||||
<span i18n>Portfolio Allocations</span>
|
<span i18n>Portfolio Allocations</span>
|
||||||
@if (hasPermissionForSubscription) {
|
@if (hasPermissionForSubscription) {
|
||||||
<gf-premium-indicator class="ml-1" />
|
<gf-premium-indicator class="ml-1" />
|
||||||
}
|
}
|
||||||
</h4>
|
</h4>
|
||||||
<p class="m-0">
|
<p class="m-0">
|
||||||
@ -197,24 +197,24 @@
|
|||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
@if (hasPermissionForSubscription) {
|
@if (hasPermissionForSubscription) {
|
||||||
<div class="col-xs-12 col-md-4 mb-3">
|
<div class="col-xs-12 col-md-4 mb-3">
|
||||||
<mat-card appearance="outlined" class="d-flex flex-column h-100">
|
<mat-card appearance="outlined" class="d-flex flex-column h-100">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<h4 class="align-items-center d-flex">
|
<h4 class="align-items-center d-flex">
|
||||||
<span i18n>Market Mood</span>
|
<span i18n>Market Mood</span>
|
||||||
<gf-premium-indicator class="ml-1" />
|
<gf-premium-indicator class="ml-1" />
|
||||||
</h4>
|
</h4>
|
||||||
<p class="m-0">
|
<p class="m-0">
|
||||||
Check the current market mood (<a
|
Check the current market mood (<a
|
||||||
[routerLink]="routerLinkResources"
|
[routerLink]="routerLinkResources"
|
||||||
>Fear & Greed Index</a
|
>Fear & Greed Index</a
|
||||||
>) within the app.
|
>) within the app.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="col-xs-12 col-md-4 mb-3">
|
<div class="col-xs-12 col-md-4 mb-3">
|
||||||
<mat-card appearance="outlined" class="d-flex flex-column h-100">
|
<mat-card appearance="outlined" class="d-flex flex-column h-100">
|
||||||
@ -223,7 +223,7 @@
|
|||||||
<h4 class="align-items-center d-flex">
|
<h4 class="align-items-center d-flex">
|
||||||
<span i18n>Static Analysis</span>
|
<span i18n>Static Analysis</span>
|
||||||
@if (hasPermissionForSubscription) {
|
@if (hasPermissionForSubscription) {
|
||||||
<gf-premium-indicator class="ml-1" />
|
<gf-premium-indicator class="ml-1" />
|
||||||
}
|
}
|
||||||
</h4>
|
</h4>
|
||||||
<p class="m-0">
|
<p class="m-0">
|
||||||
@ -290,12 +290,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (!user) {
|
@if (!user) {
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col mt-3 text-center">
|
<div class="col mt-3 text-center">
|
||||||
<a color="primary" i18n mat-flat-button [routerLink]="routerLinkRegister"
|
<a
|
||||||
>Get Started</a
|
color="primary"
|
||||||
>
|
i18n
|
||||||
|
mat-flat-button
|
||||||
|
[routerLink]="routerLinkRegister"
|
||||||
|
>Get Started</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
>
|
>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
[name]="tab.iconName"
|
[name]="tab.iconName"
|
||||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||||
/>
|
/>
|
||||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
app, asset, cryptocurrency, dashboard, etf, finance, management,
|
app, asset, cryptocurrency, dashboard, etf, finance, management,
|
||||||
performance, portfolio, software, stock, trading, wealth, web3
|
performance, portfolio, software, stock, trading, wealth, web3
|
||||||
</li>
|
</li>
|
||||||
|
<li i18n="@@myAccount">My Account</li>
|
||||||
<li i18n="@@slogan">Open Source Wealth Management Software</li>
|
<li i18n="@@slogan">Open Source Wealth Management Software</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -339,7 +339,8 @@
|
|||||||
[href]="testimonial.url"
|
[href]="testimonial.url"
|
||||||
>{{ testimonial.author }}</a
|
>{{ testimonial.author }}</a
|
||||||
>
|
>
|
||||||
<span *ngIf="!testimonial.url">{{ testimonial.author }}</span>,
|
<span *ngIf="!testimonial.url">{{ testimonial.author }}</span
|
||||||
|
>,
|
||||||
{{ testimonial.country }}
|
{{ testimonial.country }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,7 +36,6 @@ import { ImportActivitiesDialogParams } from './import-activities-dialog/interfa
|
|||||||
export class ActivitiesPageComponent implements OnDestroy, OnInit {
|
export class ActivitiesPageComponent implements OnDestroy, OnInit {
|
||||||
public activities: Activity[];
|
public activities: Activity[];
|
||||||
public dataSource: MatTableDataSource<Activity>;
|
public dataSource: MatTableDataSource<Activity>;
|
||||||
public defaultAccountId: string;
|
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
public hasPermissionToCreateActivity: boolean;
|
public hasPermissionToCreateActivity: boolean;
|
||||||
@ -323,7 +322,7 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
|
|||||||
accounts: this.user?.accounts,
|
accounts: this.user?.accounts,
|
||||||
activity: {
|
activity: {
|
||||||
...aActivity,
|
...aActivity,
|
||||||
accountId: aActivity?.accountId ?? this.defaultAccountId,
|
accountId: aActivity?.accountId,
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
id: null,
|
id: null,
|
||||||
fee: 0,
|
fee: 0,
|
||||||
@ -399,10 +398,6 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
|
|||||||
private updateUser(aUser: User) {
|
private updateUser(aUser: User) {
|
||||||
this.user = aUser;
|
this.user = aUser;
|
||||||
|
|
||||||
this.defaultAccountId = this.user?.accounts.find((account) => {
|
|
||||||
return account.isDefault;
|
|
||||||
})?.id;
|
|
||||||
|
|
||||||
this.hasPermissionToCreateActivity =
|
this.hasPermissionToCreateActivity =
|
||||||
!this.hasImpersonationId &&
|
!this.hasImpersonationId &&
|
||||||
hasPermission(this.user.permissions, permissions.createOrder);
|
hasPermission(this.user.permissions, permissions.createOrder);
|
||||||
|
@ -11,7 +11,11 @@
|
|||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[pageIndex]="pageIndex"
|
[pageIndex]="pageIndex"
|
||||||
[pageSize]="pageSize"
|
[pageSize]="pageSize"
|
||||||
[showActions]="!hasImpersonationId && hasPermissionToDeleteActivity && !user.settings.isRestrictedView"
|
[showActions]="
|
||||||
|
!hasImpersonationId &&
|
||||||
|
hasPermissionToDeleteActivity &&
|
||||||
|
!user.settings.isRestrictedView
|
||||||
|
"
|
||||||
[sortColumn]="sortColumn"
|
[sortColumn]="sortColumn"
|
||||||
[sortDirection]="sortDirection"
|
[sortDirection]="sortDirection"
|
||||||
[totalItems]="totalItems"
|
[totalItems]="totalItems"
|
||||||
@ -29,18 +33,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (!hasImpersonationId && hasPermissionToCreateActivity &&
|
@if (
|
||||||
!user.settings.isRestrictedView) {
|
!hasImpersonationId &&
|
||||||
<div class="fab-container">
|
hasPermissionToCreateActivity &&
|
||||||
<a
|
!user.settings.isRestrictedView
|
||||||
class="align-items-center d-flex justify-content-center"
|
) {
|
||||||
color="primary"
|
<div class="fab-container">
|
||||||
mat-fab
|
<a
|
||||||
[queryParams]="{ createDialog: true }"
|
class="align-items-center d-flex justify-content-center"
|
||||||
[routerLink]="[]"
|
color="primary"
|
||||||
>
|
mat-fab
|
||||||
<ion-icon name="add-outline" size="large" />
|
[queryParams]="{ createDialog: true }"
|
||||||
</a>
|
[routerLink]="[]"
|
||||||
</div>
|
>
|
||||||
|
<ion-icon name="add-outline" size="large" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,48 +11,61 @@
|
|||||||
<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 formControlName="type">
|
<mat-select formControlName="type">
|
||||||
<mat-select-trigger
|
<mat-select-trigger>{{
|
||||||
>{{ typesTranslationMap[activityForm.controls['type'].value]
|
typesTranslationMap[activityForm.controls['type'].value]
|
||||||
}}</mat-select-trigger
|
}}</mat-select-trigger>
|
||||||
>
|
|
||||||
<mat-option value="BUY">
|
<mat-option value="BUY">
|
||||||
<span><b>{{ typesTranslationMap['BUY'] }}</b></span>
|
<span
|
||||||
|
><b>{{ typesTranslationMap['BUY'] }}</b></span
|
||||||
|
>
|
||||||
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
||||||
>Stocks, ETFs, bonds, cryptocurrencies, commodities</small
|
>Stocks, ETFs, bonds, cryptocurrencies, commodities</small
|
||||||
>
|
>
|
||||||
</mat-option>
|
</mat-option>
|
||||||
<mat-option value="FEE">
|
<mat-option value="FEE">
|
||||||
<span><b>{{ typesTranslationMap['FEE'] }}</b></span>
|
<span
|
||||||
|
><b>{{ typesTranslationMap['FEE'] }}</b></span
|
||||||
|
>
|
||||||
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
||||||
>One-time fee, annual account fees</small
|
>One-time fee, annual account fees</small
|
||||||
>
|
>
|
||||||
</mat-option>
|
</mat-option>
|
||||||
<mat-option value="DIVIDEND">
|
<mat-option value="DIVIDEND">
|
||||||
<span><b>{{ typesTranslationMap['DIVIDEND'] }}</b></span>
|
<span
|
||||||
|
><b>{{ typesTranslationMap['DIVIDEND'] }}</b></span
|
||||||
|
>
|
||||||
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
||||||
>Distribution of corporate earnings</small
|
>Distribution of corporate earnings</small
|
||||||
>
|
>
|
||||||
</mat-option>
|
</mat-option>
|
||||||
<mat-option value="INTEREST">
|
<mat-option value="INTEREST">
|
||||||
<span><b>{{ typesTranslationMap['INTEREST'] }}</b></span>
|
<span
|
||||||
|
><b>{{ typesTranslationMap['INTEREST'] }}</b></span
|
||||||
|
>
|
||||||
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
||||||
>Revenue for lending out money</small
|
>Revenue for lending out money</small
|
||||||
>
|
>
|
||||||
</mat-option>
|
</mat-option>
|
||||||
<mat-option value="LIABILITY">
|
<mat-option value="LIABILITY">
|
||||||
<span><b>{{ typesTranslationMap['LIABILITY'] }}</b></span>
|
<span
|
||||||
|
><b>{{ typesTranslationMap['LIABILITY'] }}</b></span
|
||||||
|
>
|
||||||
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
||||||
>Mortgages, personal loans, credit cards</small
|
>Mortgages, personal loans, credit cards</small
|
||||||
>
|
>
|
||||||
</mat-option>
|
</mat-option>
|
||||||
<mat-option value="SELL">
|
<mat-option value="SELL">
|
||||||
<span><b>{{ typesTranslationMap['SELL'] }}</b></span>
|
<span
|
||||||
|
><b>{{ typesTranslationMap['SELL'] }}</b></span
|
||||||
|
>
|
||||||
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
||||||
>Stocks, ETFs, bonds, cryptocurrencies, commodities</small
|
>Stocks, ETFs, bonds, cryptocurrencies, commodities</small
|
||||||
>
|
>
|
||||||
</mat-option>
|
</mat-option>
|
||||||
<mat-option value="ITEM">
|
<mat-option value="ITEM">
|
||||||
<span><b>{{ typesTranslationMap['ITEM'] }}</b></span>
|
<span
|
||||||
|
><b>{{ typesTranslationMap['ITEM'] }}</b></span
|
||||||
|
>
|
||||||
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
||||||
>Luxury items, real estate, private companies</small
|
>Luxury items, real estate, private companies</small
|
||||||
>
|
>
|
||||||
@ -60,16 +73,20 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div [ngClass]="{'mb-3': data.activity.id}">
|
<div [ngClass]="{ 'mb-3': data.activity.id }">
|
||||||
<mat-form-field
|
<mat-form-field
|
||||||
appearance="outline"
|
appearance="outline"
|
||||||
class="w-100"
|
class="w-100"
|
||||||
[ngClass]="{'mb-1 without-hint': !data.activity.id}"
|
[ngClass]="{ 'mb-1 without-hint': !data.activity.id }"
|
||||||
>
|
>
|
||||||
<mat-label i18n>Account</mat-label>
|
<mat-label i18n>Account</mat-label>
|
||||||
<mat-select formControlName="accountId">
|
<mat-select formControlName="accountId">
|
||||||
<mat-option
|
<mat-option
|
||||||
*ngIf="!activityForm.controls['accountId'].hasValidator(Validators.required)"
|
*ngIf="
|
||||||
|
!activityForm.controls['accountId'].hasValidator(
|
||||||
|
Validators.required
|
||||||
|
)
|
||||||
|
"
|
||||||
[value]="null"
|
[value]="null"
|
||||||
/>
|
/>
|
||||||
<mat-option
|
<mat-option
|
||||||
@ -88,14 +105,18 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3" [ngClass]="{'d-none': data.activity.id}">
|
<div class="mb-3" [ngClass]="{ 'd-none': data.activity.id }">
|
||||||
<mat-checkbox color="primary" formControlName="updateAccountBalance" i18n
|
<mat-checkbox color="primary" formControlName="updateAccountBalance" i18n
|
||||||
>Update Cash Balance</mat-checkbox
|
>Update Cash Balance</mat-checkbox
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mb-3"
|
class="mb-3"
|
||||||
[ngClass]="{ 'd-none': !activityForm.controls['searchSymbol'].hasValidator(Validators.required) }"
|
[ngClass]="{
|
||||||
|
'd-none': !activityForm.controls['searchSymbol'].hasValidator(
|
||||||
|
Validators.required
|
||||||
|
)
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Name, symbol or ISIN</mat-label>
|
<mat-label i18n>Name, symbol or ISIN</mat-label>
|
||||||
@ -107,7 +128,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mb-3"
|
class="mb-3"
|
||||||
[ngClass]="{ 'd-none': !activityForm.controls['name'].hasValidator(Validators.required) }"
|
[ngClass]="{
|
||||||
|
'd-none': !activityForm.controls['name'].hasValidator(
|
||||||
|
Validators.required
|
||||||
|
)
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Name</mat-label>
|
<mat-label i18n>Name</mat-label>
|
||||||
@ -118,9 +143,9 @@
|
|||||||
<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 formControlName="currency">
|
<mat-select formControlName="currency">
|
||||||
<mat-option *ngFor="let currency of currencies" [value]="currency"
|
<mat-option *ngFor="let currency of currencies" [value]="currency">{{
|
||||||
>{{ currency }}</mat-option
|
currency
|
||||||
>
|
}}</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
@ -146,7 +171,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mb-3"
|
class="mb-3"
|
||||||
[ngClass]="{ 'd-none': activityForm.controls['type']?.value === 'FEE' || activityForm.controls['type']?.value === 'INTEREST' || activityForm.controls['type']?.value === 'ITEM' || activityForm.controls['type']?.value === 'LIABILITY' }"
|
[ngClass]="{
|
||||||
|
'd-none':
|
||||||
|
activityForm.controls['type']?.value === 'FEE' ||
|
||||||
|
activityForm.controls['type']?.value === 'INTEREST' ||
|
||||||
|
activityForm.controls['type']?.value === 'ITEM' ||
|
||||||
|
activityForm.controls['type']?.value === 'LIABILITY'
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Quantity</mat-label>
|
<mat-label i18n>Quantity</mat-label>
|
||||||
@ -155,7 +186,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mb-3"
|
class="mb-3"
|
||||||
[ngClass]="{ 'd-none': activityForm.controls['type']?.value === 'FEE' }"
|
[ngClass]="{ 'd-none': activityForm.controls['type']?.value === 'FEE' }"
|
||||||
>
|
>
|
||||||
<div class="align-items-start d-flex">
|
<div class="align-items-start d-flex">
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
@ -192,17 +223,25 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</div>
|
</div>
|
||||||
<mat-error
|
<mat-error
|
||||||
*ngIf="activityForm.controls['unitPriceInCustomCurrency'].hasError('invalid')"
|
*ngIf="
|
||||||
|
activityForm.controls['unitPriceInCustomCurrency'].hasError(
|
||||||
|
'invalid'
|
||||||
|
)
|
||||||
|
"
|
||||||
><ng-container i18n
|
><ng-container i18n
|
||||||
>Oops! Could not get the historical exchange rate
|
>Oops! Could not get the historical exchange rate
|
||||||
from</ng-container
|
from</ng-container
|
||||||
>
|
>
|
||||||
{{ activityForm.controls['date']?.value | date: defaultDateFormat
|
{{
|
||||||
|
activityForm.controls['date']?.value | date: defaultDateFormat
|
||||||
}}</mat-error
|
}}</mat-error
|
||||||
>
|
>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button
|
<button
|
||||||
*ngIf="currentMarketPrice && (data.activity.type === 'BUY' || data.activity.type === 'SELL')"
|
*ngIf="
|
||||||
|
currentMarketPrice &&
|
||||||
|
(data.activity.type === 'BUY' || data.activity.type === 'SELL')
|
||||||
|
"
|
||||||
class="ml-2 mt-1 no-min-width"
|
class="ml-2 mt-1 no-min-width"
|
||||||
mat-button
|
mat-button
|
||||||
title="Apply current market price"
|
title="Apply current market price"
|
||||||
@ -228,14 +267,19 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</mat-label>
|
</mat-label>
|
||||||
<input formControlName="unitPrice" matInput type="number" />
|
<input formControlName="unitPrice" matInput type="number" />
|
||||||
<span class="ml-2" matTextSuffix
|
<span class="ml-2" matTextSuffix>{{
|
||||||
>{{ activityForm.controls['currency'].value }}</span
|
activityForm.controls['currency'].value
|
||||||
>
|
}}</span>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mb-3"
|
class="mb-3"
|
||||||
[ngClass]="{ 'd-none': activityForm.controls['type']?.value === 'INTEREST' || activityForm.controls['type']?.value === 'ITEM' || activityForm.controls['type']?.value === 'LIABILITY' }"
|
[ngClass]="{
|
||||||
|
'd-none':
|
||||||
|
activityForm.controls['type']?.value === 'INTEREST' ||
|
||||||
|
activityForm.controls['type']?.value === 'ITEM' ||
|
||||||
|
activityForm.controls['type']?.value === 'LIABILITY'
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Fee</mat-label>
|
<mat-label i18n>Fee</mat-label>
|
||||||
@ -252,11 +296,14 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</div>
|
</div>
|
||||||
<mat-error
|
<mat-error
|
||||||
*ngIf="activityForm.controls['feeInCustomCurrency'].hasError('invalid')"
|
*ngIf="
|
||||||
|
activityForm.controls['feeInCustomCurrency'].hasError('invalid')
|
||||||
|
"
|
||||||
><ng-container i18n
|
><ng-container i18n
|
||||||
>Oops! Could not get the historical exchange rate from</ng-container
|
>Oops! Could not get the historical exchange rate from</ng-container
|
||||||
>
|
>
|
||||||
{{ activityForm.controls['date']?.value | date: defaultDateFormat
|
{{
|
||||||
|
activityForm.controls['date']?.value | date: defaultDateFormat
|
||||||
}}</mat-error
|
}}</mat-error
|
||||||
>
|
>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
@ -265,9 +312,9 @@
|
|||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Fee</mat-label>
|
<mat-label i18n>Fee</mat-label>
|
||||||
<input formControlName="fee" matInput type="number" />
|
<input formControlName="fee" matInput type="number" />
|
||||||
<span class="ml-2" matTextSuffix
|
<span class="ml-2" matTextSuffix>{{
|
||||||
>{{ activityForm.controls['currency'].value }}</span
|
activityForm.controls['currency'].value
|
||||||
>
|
}}</span>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@ -340,7 +387,7 @@
|
|||||||
(optionSelected)="onAddTag($event)"
|
(optionSelected)="onAddTag($event)"
|
||||||
>
|
>
|
||||||
<mat-option
|
<mat-option
|
||||||
*ngFor="let tag of filteredTagsObservable | async"
|
*ngFor="let tag of filteredTagsObservable | async"
|
||||||
[value]="tag.id"
|
[value]="tag.id"
|
||||||
>
|
>
|
||||||
{{ tag.name }}
|
{{ tag.name }}
|
||||||
@ -354,7 +401,10 @@
|
|||||||
class="flex-grow-1"
|
class="flex-grow-1"
|
||||||
[isCurrency]="true"
|
[isCurrency]="true"
|
||||||
[locale]="data.user?.settings?.locale"
|
[locale]="data.user?.settings?.locale"
|
||||||
[unit]="activityForm.controls['currency']?.value ?? data.user?.settings?.baseCurrency"
|
[unit]="
|
||||||
|
activityForm.controls['currency']?.value ??
|
||||||
|
data.user?.settings?.baseCurrency
|
||||||
|
"
|
||||||
[value]="total"
|
[value]="total"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
|
@ -25,81 +25,86 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="pt-3">
|
<div class="pt-3">
|
||||||
@if (mode === 'DIVIDEND') {
|
@if (mode === 'DIVIDEND') {
|
||||||
<form
|
<form
|
||||||
[formGroup]="uniqueAssetForm"
|
[formGroup]="uniqueAssetForm"
|
||||||
(ngSubmit)="onLoadDividends(stepper)"
|
(ngSubmit)="onLoadDividends(stepper)"
|
||||||
>
|
>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Holding</mat-label>
|
<mat-label i18n>Holding</mat-label>
|
||||||
<mat-select formControlName="uniqueAsset">
|
<mat-select formControlName="uniqueAsset">
|
||||||
<mat-select-trigger
|
<mat-select-trigger>{{
|
||||||
>{{ uniqueAssetForm.controls['uniqueAsset']?.value?.name
|
uniqueAssetForm.controls['uniqueAsset']?.value?.name
|
||||||
}}</mat-select-trigger
|
}}</mat-select-trigger>
|
||||||
>
|
<mat-option
|
||||||
<mat-option
|
*ngFor="let holding of holdings"
|
||||||
*ngFor="let holding of holdings"
|
class="line-height-1"
|
||||||
class="line-height-1"
|
[value]="{
|
||||||
[value]="{ dataSource: holding.dataSource, name: holding.name, symbol: holding.symbol }"
|
dataSource: holding.dataSource,
|
||||||
>
|
name: holding.name,
|
||||||
<span><b>{{ holding.name }}</b></span>
|
symbol: holding.symbol
|
||||||
<br />
|
}"
|
||||||
<small class="text-muted"
|
|
||||||
>{{ holding.symbol | gfSymbol }} · {{ holding.currency
|
|
||||||
}}</small
|
|
||||||
>
|
>
|
||||||
</mat-option>
|
<span
|
||||||
</mat-select>
|
><b>{{ holding.name }}</b></span
|
||||||
<mat-spinner
|
>
|
||||||
*ngIf="isLoading"
|
<br />
|
||||||
class="position-absolute"
|
<small class="text-muted"
|
||||||
[diameter]="20"
|
>{{ holding.symbol | gfSymbol }} ·
|
||||||
/>
|
{{ holding.currency }}</small
|
||||||
</mat-form-field>
|
>
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
<mat-spinner
|
||||||
|
*ngIf="isLoading"
|
||||||
|
class="position-absolute"
|
||||||
|
[diameter]="20"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
<div class="d-flex flex-column justify-content-center">
|
||||||
|
<button
|
||||||
|
color="primary"
|
||||||
|
mat-flat-button
|
||||||
|
type="submit"
|
||||||
|
[disabled]="!uniqueAssetForm.valid"
|
||||||
|
>
|
||||||
|
<span i18n>Load Dividends</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
} @else {
|
||||||
<div class="d-flex flex-column justify-content-center">
|
<div class="d-flex flex-column justify-content-center">
|
||||||
<button
|
<button
|
||||||
color="primary"
|
class="drop-area p-4 text-center text-muted"
|
||||||
mat-flat-button
|
gfFileDrop
|
||||||
type="submit"
|
(click)="onSelectFile(stepper)"
|
||||||
[disabled]="!uniqueAssetForm.valid"
|
(filesDropped)="onFilesDropped({ stepper, files: $event })"
|
||||||
>
|
>
|
||||||
<span i18n>Load Dividends</span>
|
<div
|
||||||
|
class="align-items-center d-flex flex-column justify-content-center"
|
||||||
|
>
|
||||||
|
<ion-icon class="cloud-icon" name="cloud-upload-outline" />
|
||||||
|
<span i18n>Choose or drop a file here</span>
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
<p class="mb-0 mt-3 text-center">
|
||||||
|
<small>
|
||||||
|
<span class="mr-1" i18n
|
||||||
|
>The following file formats are supported:</span
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok.csv"
|
||||||
|
target="_blank"
|
||||||
|
>CSV</a
|
||||||
|
>
|
||||||
|
<span class="mx-1" i18n>or</span>
|
||||||
|
<a
|
||||||
|
href="https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok.json"
|
||||||
|
target="_blank"
|
||||||
|
>JSON</a
|
||||||
|
>
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
} @else {
|
|
||||||
<div class="d-flex flex-column justify-content-center">
|
|
||||||
<button
|
|
||||||
class="drop-area p-4 text-center text-muted"
|
|
||||||
gfFileDrop
|
|
||||||
(click)="onSelectFile(stepper)"
|
|
||||||
(filesDropped)="onFilesDropped({stepper, files: $event})"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="align-items-center d-flex flex-column justify-content-center"
|
|
||||||
>
|
|
||||||
<ion-icon class="cloud-icon" name="cloud-upload-outline" />
|
|
||||||
<span i18n>Choose or drop a file here</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<p class="mb-0 mt-3 text-center">
|
|
||||||
<small>
|
|
||||||
<span class="mr-1" i18n
|
|
||||||
>The following file formats are supported:</span
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok.csv"
|
|
||||||
target="_blank"
|
|
||||||
>CSV</a
|
|
||||||
>
|
|
||||||
<span class="mx-1" i18n>or</span>
|
|
||||||
<a
|
|
||||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok.json"
|
|
||||||
target="_blank"
|
|
||||||
>JSON</a
|
|
||||||
>
|
|
||||||
</small>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
@ -114,76 +119,76 @@
|
|||||||
>
|
>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="pt-3">
|
<div class="pt-3">
|
||||||
@if(errorMessages?.length === 0) {
|
@if (errorMessages?.length === 0) {
|
||||||
<gf-activities-table
|
<gf-activities-table
|
||||||
*ngIf="importStep === 1"
|
*ngIf="importStep === 1"
|
||||||
[baseCurrency]="data?.user?.settings?.baseCurrency"
|
[baseCurrency]="data?.user?.settings?.baseCurrency"
|
||||||
[dataSource]="dataSource"
|
[dataSource]="dataSource"
|
||||||
[deviceType]="data?.deviceType"
|
[deviceType]="data?.deviceType"
|
||||||
[hasPermissionToCreateActivity]="false"
|
[hasPermissionToCreateActivity]="false"
|
||||||
[hasPermissionToExportActivities]="false"
|
[hasPermissionToExportActivities]="false"
|
||||||
[hasPermissionToFilter]="false"
|
[hasPermissionToFilter]="false"
|
||||||
[hasPermissionToOpenDetails]="false"
|
[hasPermissionToOpenDetails]="false"
|
||||||
[locale]="data?.user?.settings?.locale"
|
[locale]="data?.user?.settings?.locale"
|
||||||
[pageSize]="maxSafeInteger"
|
[pageSize]="maxSafeInteger"
|
||||||
[showActions]="false"
|
[showActions]="false"
|
||||||
[showCheckbox]="true"
|
[showCheckbox]="true"
|
||||||
[showSymbolColumn]="false"
|
[showSymbolColumn]="false"
|
||||||
[sortColumn]="sortColumn"
|
[sortColumn]="sortColumn"
|
||||||
[sortDirection]="sortDirection"
|
[sortDirection]="sortDirection"
|
||||||
[sortDisabled]="true"
|
[sortDisabled]="true"
|
||||||
[totalItems]="totalItems"
|
[totalItems]="totalItems"
|
||||||
(selectedActivities)="updateSelection($event)"
|
(selectedActivities)="updateSelection($event)"
|
||||||
/>
|
/>
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<div class="d-flex justify-content-end mt-3">
|
||||||
<button mat-button (click)="onReset(stepper)">
|
<button mat-button (click)="onReset(stepper)">
|
||||||
<ng-container i18n>Back</ng-container>
|
<ng-container i18n>Back</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="ml-1"
|
class="ml-1"
|
||||||
color="primary"
|
color="primary"
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[disabled]="!selectedActivities?.length"
|
[disabled]="!selectedActivities?.length"
|
||||||
(click)="onImportActivities()"
|
(click)="onImportActivities()"
|
||||||
>
|
>
|
||||||
<ng-container i18n>Import</ng-container>
|
<ng-container i18n>Import</ng-container>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<mat-accordion displayMode="flat">
|
<mat-accordion displayMode="flat">
|
||||||
<mat-expansion-panel
|
<mat-expansion-panel
|
||||||
*ngFor="let message of errorMessages; let i = index"
|
*ngFor="let message of errorMessages; let i = index"
|
||||||
[disabled]="!details[i]"
|
[disabled]="!details[i]"
|
||||||
>
|
>
|
||||||
<mat-expansion-panel-header class="pl-1">
|
<mat-expansion-panel-header class="pl-1">
|
||||||
<mat-panel-title>
|
<mat-panel-title>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<div class="align-items-center d-flex mr-2">
|
<div class="align-items-center d-flex mr-2">
|
||||||
<ion-icon name="warning-outline" />
|
<ion-icon name="warning-outline" />
|
||||||
|
</div>
|
||||||
|
<div>{{ message }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>{{ message }}</div>
|
</mat-panel-title>
|
||||||
</div>
|
</mat-expansion-panel-header>
|
||||||
</mat-panel-title>
|
<pre
|
||||||
</mat-expansion-panel-header>
|
*ngIf="details[i]"
|
||||||
<pre
|
class="m-0"
|
||||||
*ngIf="details[i]"
|
><code>{{ details[i] | json }}</code></pre>
|
||||||
class="m-0"
|
</mat-expansion-panel>
|
||||||
><code>{{ details[i] | json }}</code></pre>
|
</mat-accordion>
|
||||||
</mat-expansion-panel>
|
<div class="d-flex justify-content-end mt-3">
|
||||||
</mat-accordion>
|
<button mat-button (click)="onReset(stepper)">
|
||||||
<div class="d-flex justify-content-end mt-3">
|
<ng-container i18n>Back</ng-container>
|
||||||
<button mat-button (click)="onReset(stepper)">
|
</button>
|
||||||
<ng-container i18n>Back</ng-container>
|
<button
|
||||||
</button>
|
class="ml-1"
|
||||||
<button
|
color="primary"
|
||||||
class="ml-1"
|
mat-flat-button
|
||||||
color="primary"
|
[disabled]="true"
|
||||||
mat-flat-button
|
>
|
||||||
[disabled]="true"
|
<ng-container i18n>Import</ng-container>
|
||||||
>
|
</button>
|
||||||
<ng-container i18n>Import</ng-container>
|
</div>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
|
@ -15,13 +15,20 @@
|
|||||||
class="justify-content-end l-2"
|
class="justify-content-end l-2"
|
||||||
size="medium"
|
size="medium"
|
||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[value]="isLoading ? undefined : portfolioDetails?.filteredValueInPercentage"
|
[value]="
|
||||||
|
isLoading
|
||||||
|
? undefined
|
||||||
|
: portfolioDetails?.filteredValueInPercentage
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<mat-progress-bar
|
<mat-progress-bar
|
||||||
mode="determinate"
|
mode="determinate"
|
||||||
[title]="(portfolioDetails?.filteredValueInPercentage * 100).toFixed(2) + '%'"
|
[title]="
|
||||||
|
(portfolioDetails?.filteredValueInPercentage * 100).toFixed(2) +
|
||||||
|
'%'
|
||||||
|
"
|
||||||
[value]="portfolioDetails?.filteredValueInPercentage * 100"
|
[value]="portfolioDetails?.filteredValueInPercentage * 100"
|
||||||
/>
|
/>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
@ -204,7 +211,9 @@
|
|||||||
<gf-world-map-chart
|
<gf-world-map-chart
|
||||||
[countries]="countries"
|
[countries]="countries"
|
||||||
[format]="worldMapChartFormat"
|
[format]="worldMapChartFormat"
|
||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="
|
||||||
|
hasImpersonationId || user.settings.isRestrictedView
|
||||||
|
"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -270,23 +270,28 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
index,
|
index,
|
||||||
{
|
{
|
||||||
date,
|
date,
|
||||||
netPerformanceInPercentage,
|
netPerformanceInPercentageWithCurrencyEffect,
|
||||||
totalInvestment,
|
totalInvestmentValueWithCurrencyEffect,
|
||||||
value,
|
valueInPercentage,
|
||||||
valueInPercentage
|
valueWithCurrencyEffect
|
||||||
}
|
}
|
||||||
] of chart.entries()) {
|
] of chart.entries()) {
|
||||||
if (index > 0 || this.user?.settings?.dateRange === 'max') {
|
if (index > 0 || this.user?.settings?.dateRange === 'max') {
|
||||||
// Ignore first item where value is 0
|
// Ignore first item where value is 0
|
||||||
this.investments.push({ date, investment: totalInvestment });
|
this.investments.push({
|
||||||
|
date,
|
||||||
|
investment: totalInvestmentValueWithCurrencyEffect
|
||||||
|
});
|
||||||
this.performanceDataItems.push({
|
this.performanceDataItems.push({
|
||||||
date,
|
date,
|
||||||
value: isNumber(value) ? value : valueInPercentage
|
value: isNumber(valueWithCurrencyEffect)
|
||||||
|
? valueWithCurrencyEffect
|
||||||
|
: valueInPercentage
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.performanceDataItemsInPercentage.push({
|
this.performanceDataItemsInPercentage.push({
|
||||||
date,
|
date,
|
||||||
value: netPerformanceInPercentage
|
value: netPerformanceInPercentageWithCurrencyEffect
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,10 +310,10 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(({ positions }) => {
|
.subscribe(({ positions }) => {
|
||||||
const positionsSorted = sortBy(
|
const positionsSorted = sortBy(
|
||||||
positions.filter(({ netPerformancePercentage }) => {
|
positions.filter(({ netPerformancePercentageWithCurrencyEffect }) => {
|
||||||
return isNumber(netPerformancePercentage);
|
return isNumber(netPerformancePercentageWithCurrencyEffect);
|
||||||
}),
|
}),
|
||||||
'netPerformancePercentage'
|
'netPerformancePercentageWithCurrencyEffect'
|
||||||
).reverse();
|
).reverse();
|
||||||
|
|
||||||
this.top3 = positionsSorted.slice(0, 3);
|
this.top3 = positionsSorted.slice(0, 3);
|
||||||
|
@ -18,14 +18,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (user?.settings?.isExperimentalFeatures) {
|
|
||||||
<div class="mb-5 row">
|
<div class="mb-5 row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<mat-card appearance="outlined" class="mb-3">
|
<mat-card appearance="outlined" class="mb-3">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<div class="d-flex py-1">
|
<div class="d-flex py-1">
|
||||||
<div class="flex-grow-1 mr-2 text-truncate" i18n>
|
<div
|
||||||
Absolute Asset Performance
|
class="align-items-center d-flex flex-grow-1 mr-2 text-truncate"
|
||||||
|
>
|
||||||
|
<span i18n>Absolute Asset Performance</span>
|
||||||
|
<gf-premium-indicator
|
||||||
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
|
class="ml-1"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
<gf-value
|
<gf-value
|
||||||
@ -34,7 +39,11 @@
|
|||||||
[isCurrency]="true"
|
[isCurrency]="true"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[unit]="user?.settings?.baseCurrency"
|
[unit]="user?.settings?.baseCurrency"
|
||||||
[value]="isLoadingInvestmentChart ? undefined : performance?.currentNetPerformance"
|
[value]="
|
||||||
|
isLoadingInvestmentChart
|
||||||
|
? undefined
|
||||||
|
: performance?.currentNetPerformance
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -49,13 +58,23 @@
|
|||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[value]="isLoadingInvestmentChart ? undefined : performance?.currentNetPerformancePercent"
|
[value]="
|
||||||
|
isLoadingInvestmentChart
|
||||||
|
? undefined
|
||||||
|
: performance?.currentNetPerformancePercent
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex py-1">
|
<div class="d-flex py-1">
|
||||||
<div class="flex-grow-1 mr-2 text-truncate" i18n>
|
<div
|
||||||
Absolute Currency Performance
|
class="align-items-center d-flex flex-grow-1 mr-2 text-truncate"
|
||||||
|
>
|
||||||
|
<span i18n>Absolute Currency Performance</span>
|
||||||
|
<gf-premium-indicator
|
||||||
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
|
class="ml-1"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
<gf-value
|
<gf-value
|
||||||
@ -64,7 +83,14 @@
|
|||||||
[isCurrency]="true"
|
[isCurrency]="true"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[unit]="user?.settings?.baseCurrency"
|
[unit]="user?.settings?.baseCurrency"
|
||||||
[value]="isLoadingInvestmentChart ? undefined : (performance?.currentNetPerformanceWithCurrencyEffect === null ? null : performance?.currentNetPerformanceWithCurrencyEffect - performance?.currentNetPerformance)"
|
[value]="
|
||||||
|
isLoadingInvestmentChart
|
||||||
|
? undefined
|
||||||
|
: performance?.currentNetPerformance === null
|
||||||
|
? null
|
||||||
|
: performance?.currentNetPerformanceWithCurrencyEffect -
|
||||||
|
performance?.currentNetPerformance
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -79,7 +105,14 @@
|
|||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[value]="isLoadingInvestmentChart ? undefined : performance?.currentNetPerformancePercentWithCurrencyEffect - performance?.currentNetPerformancePercent"
|
[value]="
|
||||||
|
isLoadingInvestmentChart
|
||||||
|
? undefined
|
||||||
|
: performance?.currentNetPerformancePercent === null
|
||||||
|
? null
|
||||||
|
: performance?.currentNetPerformancePercentWithCurrencyEffect -
|
||||||
|
performance?.currentNetPerformancePercent
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -95,7 +128,11 @@
|
|||||||
[isCurrency]="true"
|
[isCurrency]="true"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[unit]="user?.settings?.baseCurrency"
|
[unit]="user?.settings?.baseCurrency"
|
||||||
[value]="isLoadingInvestmentChart ? undefined : performance?.currentNetPerformanceWithCurrencyEffect"
|
[value]="
|
||||||
|
isLoadingInvestmentChart
|
||||||
|
? undefined
|
||||||
|
: performance?.currentNetPerformanceWithCurrencyEffect
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -110,7 +147,11 @@
|
|||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[value]="isLoadingInvestmentChart ? undefined : performance?.currentNetPerformancePercentWithCurrencyEffect"
|
[value]="
|
||||||
|
isLoadingInvestmentChart
|
||||||
|
? undefined
|
||||||
|
: performance?.currentNetPerformancePercentWithCurrencyEffect
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -118,7 +159,6 @@
|
|||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
|
|
||||||
<div class="mb-5 row">
|
<div class="mb-5 row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@ -148,7 +188,9 @@
|
|||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[value]="position.netPerformancePercentage"
|
[value]="
|
||||||
|
position.netPerformancePercentageWithCurrencyEffect
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@ -194,7 +236,9 @@
|
|||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[value]="position.netPerformancePercentage"
|
[value]="
|
||||||
|
position.netPerformancePercentageWithCurrencyEffect
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -16,11 +16,14 @@
|
|||||||
[currency]="user?.settings?.baseCurrency"
|
[currency]="user?.settings?.baseCurrency"
|
||||||
[deviceType]="deviceType"
|
[deviceType]="deviceType"
|
||||||
[fireWealth]="fireWealth?.toNumber()"
|
[fireWealth]="fireWealth?.toNumber()"
|
||||||
[hasPermissionToUpdateUserSettings]="!hasImpersonationId && hasPermissionToUpdateUserSettings"
|
[hasPermissionToUpdateUserSettings]="
|
||||||
|
!hasImpersonationId && hasPermissionToUpdateUserSettings
|
||||||
|
"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[ngStyle]="{
|
[ngStyle]="{
|
||||||
opacity: user?.subscription?.type === 'Basic' ? '0.67' : 'initial',
|
opacity: user?.subscription?.type === 'Basic' ? '0.67' : 'initial',
|
||||||
'pointer-events': user?.subscription?.type === 'Basic' ? 'none' : 'initial'
|
'pointer-events':
|
||||||
|
user?.subscription?.type === 'Basic' ? 'none' : 'initial'
|
||||||
}"
|
}"
|
||||||
[projectedTotalAmount]="user?.settings?.projectedTotalAmount"
|
[projectedTotalAmount]="user?.settings?.projectedTotalAmount"
|
||||||
[retirementDate]="user?.settings?.retirementDate"
|
[retirementDate]="user?.settings?.retirementDate"
|
||||||
|
@ -14,15 +14,15 @@
|
|||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
/>
|
/>
|
||||||
@if (hasPermissionToCreateOrder && holdings?.length > 0) {
|
@if (hasPermissionToCreateOrder && holdings?.length > 0) {
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a
|
<a
|
||||||
class="mt-3"
|
class="mt-3"
|
||||||
i18n
|
i18n
|
||||||
mat-stroked-button
|
mat-stroked-button
|
||||||
[routerLink]="['/portfolio', 'activities']"
|
[routerLink]="['/portfolio', 'activities']"
|
||||||
>Manage Activities</a
|
>Manage Activities</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
>
|
>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
[name]="tab.iconName"
|
[name]="tab.iconName"
|
||||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||||
/>
|
/>
|
||||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -107,7 +107,7 @@
|
|||||||
<mat-card
|
<mat-card
|
||||||
appearance="outlined"
|
appearance="outlined"
|
||||||
class="h-100"
|
class="h-100"
|
||||||
[ngClass]="{ 'active': user?.subscription?.type === 'Basic' }"
|
[ngClass]="{ active: user?.subscription?.type === 'Basic' }"
|
||||||
>
|
>
|
||||||
<mat-card-content class="d-flex flex-column h-100">
|
<mat-card-content class="d-flex flex-column h-100">
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
@ -164,7 +164,7 @@
|
|||||||
<mat-card
|
<mat-card
|
||||||
appearance="outlined"
|
appearance="outlined"
|
||||||
class="h-100"
|
class="h-100"
|
||||||
[ngClass]="{ 'active': user?.subscription?.type === 'Premium' }"
|
[ngClass]="{ active: user?.subscription?.type === 'Premium' }"
|
||||||
>
|
>
|
||||||
<mat-card-content class="d-flex flex-column h-100">
|
<mat-card-content class="d-flex flex-column h-100">
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
@ -243,19 +243,22 @@
|
|||||||
<ng-container *ngIf="coupon"
|
<ng-container *ngIf="coupon"
|
||||||
><del class="text-muted"
|
><del class="text-muted"
|
||||||
>{{ baseCurrency }} {{ price }}</del
|
>{{ baseCurrency }} {{ price }}</del
|
||||||
> {{ baseCurrency }} <strong
|
> {{ baseCurrency }} <strong>{{
|
||||||
>{{ price - coupon }}</strong
|
price - coupon
|
||||||
>
|
}}</strong>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!coupon"
|
<ng-container *ngIf="!coupon"
|
||||||
>{{ baseCurrency }} <strong
|
>{{ baseCurrency }} <strong>{{
|
||||||
>{{ price }}</strong
|
price
|
||||||
></ng-container
|
}}</strong></ng-container
|
||||||
> <span i18n>per year</span></span
|
> <span i18n>per year</span></span
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
*ngIf="hasPermissionToUpdateUserSettings && user?.subscription?.type === 'Basic'"
|
*ngIf="
|
||||||
|
hasPermissionToUpdateUserSettings &&
|
||||||
|
user?.subscription?.type === 'Basic'
|
||||||
|
"
|
||||||
class="mt-3 text-center"
|
class="mt-3 text-center"
|
||||||
>
|
>
|
||||||
<button color="primary" mat-flat-button (click)="onCheckout()">
|
<button color="primary" mat-flat-button (click)="onCheckout()">
|
||||||
@ -265,7 +268,10 @@
|
|||||||
>Upgrade Plan</ng-container
|
>Upgrade Plan</ng-container
|
||||||
>
|
>
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngIf="user.subscription.offer === 'renewal' || user.subscription.offer === 'renewal-early-bird'"
|
*ngIf="
|
||||||
|
user.subscription.offer === 'renewal' ||
|
||||||
|
user.subscription.offer === 'renewal-early-bird'
|
||||||
|
"
|
||||||
i18n
|
i18n
|
||||||
>Renew Plan</ng-container
|
>Renew Plan</ng-container
|
||||||
>
|
>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<h1 mat-dialog-title>
|
<h1 mat-dialog-title>
|
||||||
<span i18n>Create Account</span
|
<span i18n>Create Account</span
|
||||||
><span *ngIf="data.role === 'ADMIN'" class="badge badge-light ml-2"
|
><span *ngIf="data.role === 'ADMIN'" class="badge badge-light ml-2">{{
|
||||||
>{{ data.role }}</span
|
data.role
|
||||||
>
|
}}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="py-3" mat-dialog-content>
|
<div class="py-3" mat-dialog-content>
|
||||||
<div>
|
<div>
|
||||||
|
@ -19,32 +19,38 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@for (product of products; track product) {
|
@for (product of products; track product) {
|
||||||
<mat-card appearance="outlined" class="mb-3">
|
<mat-card appearance="outlined" class="mb-3">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<div class="container p-0">
|
<div class="container p-0">
|
||||||
<div class="flex-nowrap no-gutters row">
|
<div class="flex-nowrap no-gutters row">
|
||||||
<a
|
<a
|
||||||
class="d-flex overflow-hidden w-100"
|
class="d-flex overflow-hidden w-100"
|
||||||
title="Compare Ghostfolio to {{ product.name }} - {{ product.slogan }}"
|
title="Compare Ghostfolio to {{ product.name }} - {{
|
||||||
[routerLink]="[pathResources, 'personal-finance-tools', pathAlternativeTo + (product.alias ?? product.key)]"
|
product.slogan
|
||||||
>
|
}}"
|
||||||
<div class="flex-grow-1 overflow-hidden">
|
[routerLink]="[
|
||||||
<div class="h6 m-0 text-truncate" i18n>
|
pathResources,
|
||||||
Open Source Alternative to {{ product.name }}
|
'personal-finance-tools',
|
||||||
|
pathAlternativeTo + (product.alias ?? product.key)
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div class="flex-grow-1 overflow-hidden">
|
||||||
|
<div class="h6 m-0 text-truncate" i18n>
|
||||||
|
Open Source Alternative to {{ product.name }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="align-items-center d-flex">
|
||||||
<div class="align-items-center d-flex">
|
<ion-icon
|
||||||
<ion-icon
|
class="chevron text-muted"
|
||||||
class="chevron text-muted"
|
name="chevron-forward-outline"
|
||||||
name="chevron-forward-outline"
|
size="small"
|
||||||
size="small"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</mat-card-content>
|
||||||
</mat-card-content>
|
</mat-card>
|
||||||
</mat-card>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,8 +11,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<section class="mb-4">
|
<section class="mb-4">
|
||||||
<p i18n>
|
<p i18n>
|
||||||
Are you looking for an open source alternative to {{ product2.name
|
Are you looking for an open source alternative to
|
||||||
}}? <a [routerLink]="routerLinkAbout">Ghostfolio</a> is a powerful
|
{{ product2.name }}?
|
||||||
|
<a [routerLink]="routerLinkAbout">Ghostfolio</a> is a powerful
|
||||||
portfolio management tool that provides individuals with a
|
portfolio management tool that provides individuals with a
|
||||||
comprehensive platform to track, analyze, and optimize their
|
comprehensive platform to track, analyze, and optimize their
|
||||||
investments. Whether you are an experienced investor or just
|
investments. Whether you are an experienced investor or just
|
||||||
@ -35,18 +36,22 @@
|
|||||||
its capabilities, security, and user experience.
|
its capabilities, security, and user experience.
|
||||||
</p>
|
</p>
|
||||||
<p i18n>
|
<p i18n>
|
||||||
Let’s dive deeper into the detailed Ghostfolio vs {{ product2.name
|
Let’s dive deeper into the detailed Ghostfolio vs
|
||||||
}} comparison table below to gain a thorough understanding of how
|
{{ product2.name }} comparison table below to gain a thorough
|
||||||
Ghostfolio positions itself relative to {{ product2.name }}. We will
|
understanding of how Ghostfolio positions itself relative to
|
||||||
explore various aspects such as features, data privacy, pricing, and
|
{{ product2.name }}. We will explore various aspects such as
|
||||||
more, allowing you to make a well-informed choice for your personal
|
features, data privacy, pricing, and more, allowing you to make a
|
||||||
requirements.
|
well-informed choice for your personal requirements.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<section class="mb-4">
|
<section class="mb-4">
|
||||||
<table class="gf-table w-100">
|
<table class="gf-table w-100">
|
||||||
<caption class="text-center" i18n>
|
<caption class="text-center" i18n>
|
||||||
Ghostfolio vs {{ product2.name }} comparison table
|
Ghostfolio vs
|
||||||
|
{{
|
||||||
|
product2.name
|
||||||
|
}}
|
||||||
|
comparison table
|
||||||
</caption>
|
</caption>
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="mat-mdc-header-row">
|
<tr class="mat-mdc-header-row">
|
||||||
@ -187,8 +192,9 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="mat-mdc-cell px-1 py-2">
|
<td class="mat-mdc-cell px-1 py-2">
|
||||||
<ng-container *ngIf="product2.pricingPerYear"
|
<ng-container *ngIf="product2.pricingPerYear"
|
||||||
><span i18n>Starting from</span> {{ product2.pricingPerYear
|
><span i18n>Starting from</span>
|
||||||
}} / <span i18n>year</span></ng-container
|
{{ product2.pricingPerYear }} /
|
||||||
|
<span i18n>year</span></ng-container
|
||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -202,13 +208,13 @@
|
|||||||
</section>
|
</section>
|
||||||
<section class="mb-4">
|
<section class="mb-4">
|
||||||
<p i18n>
|
<p i18n>
|
||||||
Please note that the information provided in the Ghostfolio vs {{
|
Please note that the information provided in the Ghostfolio vs
|
||||||
product2.name }} comparison table is based on our independent
|
{{ product2.name }} comparison table is based on our independent
|
||||||
research and analysis. This website is not affiliated with {{
|
research and analysis. This website is not affiliated with
|
||||||
product2.name }} or any other product mentioned in the comparison.
|
{{ product2.name }} or any other product mentioned in the
|
||||||
As the landscape of personal finance tools evolves, it is essential
|
comparison. As the landscape of personal finance tools evolves, it
|
||||||
to verify any specific details or changes directly from the
|
is essential to verify any specific details or changes directly from
|
||||||
respective product page. Data needs a refresh? Help us maintain
|
the respective product page. Data needs a refresh? Help us maintain
|
||||||
accurate data on
|
accurate data on
|
||||||
<a href="https://github.com/ghostfolio/ghostfolio">GitHub</a>.
|
<a href="https://github.com/ghostfolio/ghostfolio">GitHub</a>.
|
||||||
</p>
|
</p>
|
||||||
|
@ -171,21 +171,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (hasPermissionForSubscription) {
|
@if (hasPermissionForSubscription) {
|
||||||
<div class="mb-4 media">
|
<div class="mb-4 media">
|
||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
<h3 class="h5 mt-0">Personal Finance Tools</h3>
|
<h3 class="h5 mt-0">Personal Finance Tools</h3>
|
||||||
<div class="mb-1">
|
<div class="mb-1">
|
||||||
Personal finance tools are software applications that help
|
Personal finance tools are software applications that help
|
||||||
individuals manage their money, track expenses, set budgets,
|
individuals manage their money, track expenses, set budgets,
|
||||||
monitor investments, and make informed financial decisions.
|
monitor investments, and make informed financial decisions.
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a [routerLink]="routerLinkResourcesPersonalFinanceTools"
|
<a [routerLink]="routerLinkResourcesPersonalFinanceTools"
|
||||||
>Personal Finance Tools →</a
|
>Personal Finance Tools →</a
|
||||||
>
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
<div class="mb-4 media">
|
<div class="mb-4 media">
|
||||||
<div class="media-body">
|
<div class="media-body">
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
>
|
>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
[name]="tab.iconName"
|
[name]="tab.iconName"
|
||||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||||
/>
|
/>
|
||||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -8,29 +8,29 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (!hasError) {
|
@if (!hasError) {
|
||||||
<div class="col d-flex justify-content-center">
|
<div class="col d-flex justify-content-center">
|
||||||
<mat-spinner [diameter]="20" />
|
<mat-spinner [diameter]="20" />
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<div
|
<div
|
||||||
class="align-items-center col d-flex flex-column justify-content-center"
|
class="align-items-center col d-flex flex-column justify-content-center"
|
||||||
>
|
|
||||||
<h1 class="d-flex h5 justify-content-center mb-0 text-center">
|
|
||||||
<ng-container i18n>Oops, authentication has failed.</ng-container>
|
|
||||||
</h1>
|
|
||||||
<button
|
|
||||||
class="mb-3 mt-4"
|
|
||||||
color="primary"
|
|
||||||
mat-flat-button
|
|
||||||
(click)="signIn()"
|
|
||||||
>
|
>
|
||||||
<ng-container i18n>Try again</ng-container>
|
<h1 class="d-flex h5 justify-content-center mb-0 text-center">
|
||||||
</button>
|
<ng-container i18n>Oops, authentication has failed.</ng-container>
|
||||||
<div class="text-muted"><ng-container i18n>or</ng-container></div>
|
</h1>
|
||||||
<button class="mt-1" mat-flat-button (click)="deregisterDevice()">
|
<button
|
||||||
<ng-container i18n>Go back to Home Page</ng-container>
|
class="mb-3 mt-4"
|
||||||
</button>
|
color="primary"
|
||||||
</div>
|
mat-flat-button
|
||||||
|
(click)="signIn()"
|
||||||
|
>
|
||||||
|
<ng-container i18n>Try again</ng-container>
|
||||||
|
</button>
|
||||||
|
<div class="text-muted"><ng-container i18n>or</ng-container></div>
|
||||||
|
<button class="mt-1" mat-flat-button (click)="deregisterDevice()">
|
||||||
|
<ng-container i18n>Go back to Home Page</ng-container>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
>
|
>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
[name]="tab.iconName"
|
[name]="tab.iconName"
|
||||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||||
/>
|
/>
|
||||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||||
</a>
|
</a>
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,7 @@ import {
|
|||||||
DATE_FORMAT_MONTHLY,
|
DATE_FORMAT_MONTHLY,
|
||||||
DATE_FORMAT_YEARLY,
|
DATE_FORMAT_YEARLY,
|
||||||
getBackgroundColor,
|
getBackgroundColor,
|
||||||
|
getLocale,
|
||||||
getTextColor
|
getTextColor
|
||||||
} from './helper';
|
} from './helper';
|
||||||
import { ColorScheme, GroupBy } from './types';
|
import { ColorScheme, GroupBy } from './types';
|
||||||
@ -30,7 +31,7 @@ export function getTooltipOptions({
|
|||||||
colorScheme,
|
colorScheme,
|
||||||
currency = '',
|
currency = '',
|
||||||
groupBy,
|
groupBy,
|
||||||
locale = '',
|
locale = getLocale(),
|
||||||
unit = ''
|
unit = ''
|
||||||
}: {
|
}: {
|
||||||
colorScheme?: ColorScheme;
|
colorScheme?: ColorScheme;
|
||||||
|
@ -217,9 +217,7 @@ export function getEmojiFlag(aCountryCode: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getLocale() {
|
export function getLocale() {
|
||||||
return navigator.languages?.length
|
return navigator.language ?? locale;
|
||||||
? navigator.languages[0]
|
|
||||||
: navigator.language ?? locale;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNumberFormatDecimal(aLocale?: string) {
|
export function getNumberFormatDecimal(aLocale?: string) {
|
||||||
@ -230,7 +228,7 @@ export function getNumberFormatDecimal(aLocale?: string) {
|
|||||||
}).value;
|
}).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNumberFormatGroup(aLocale?: string) {
|
export function getNumberFormatGroup(aLocale = getLocale()) {
|
||||||
const formatObject = new Intl.NumberFormat(aLocale).formatToParts(9999.99);
|
const formatObject = new Intl.NumberFormat(aLocale).formatToParts(9999.99);
|
||||||
|
|
||||||
return formatObject.find((object) => {
|
return formatObject.find((object) => {
|
||||||
|
@ -5,7 +5,7 @@ export interface Export {
|
|||||||
date: string;
|
date: string;
|
||||||
version: string;
|
version: string;
|
||||||
};
|
};
|
||||||
accounts: Omit<Account, 'createdAt' | 'isDefault' | 'updatedAt' | 'userId'>[];
|
accounts: Omit<Account, 'createdAt' | 'updatedAt' | 'userId'>[];
|
||||||
activities: (Omit<
|
activities: (Omit<
|
||||||
Order,
|
Order,
|
||||||
| 'accountUserId'
|
| 'accountUserId'
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user