Compare commits
74 Commits
Author | SHA1 | Date | |
---|---|---|---|
f72d31bab3 | |||
4c893c4dcc | |||
ffb11cd10e | |||
d424b7731e | |||
6043c87481 | |||
fca0a688b6 | |||
5c6cc4fed5 | |||
64a7d38ff9 | |||
68d0d39161 | |||
233a8a8a18 | |||
190779ee35 | |||
6ef8121561 | |||
58bf57d1e6 | |||
71c5412dd5 | |||
ae85398c3d | |||
048900d01b | |||
074b09b543 | |||
f9e04022f4 | |||
8fd1fbd44a | |||
0fb33ae71c | |||
3a35d72ec2 | |||
32fe3e195f | |||
805f4b05be | |||
5b51a6840a | |||
36bd6164e6 | |||
eac52a215b | |||
9ff8cd5471 | |||
33cc7e4e7e | |||
47f84dab06 | |||
384d18b2a6 | |||
2363983bdc | |||
4af76764be | |||
a65424aafa | |||
f9cd629470 | |||
ccb8c86596 | |||
246de7aa86 | |||
a323313c71 | |||
538c8947cd | |||
1ec5fd12fe | |||
4376b8903e | |||
a8e096f9ac | |||
8e577592f6 | |||
c896bf9199 | |||
16145f18d9 | |||
5398da0dc8 | |||
2466f4ff5d | |||
8f3a9bdfbb | |||
44dfd2bd48 | |||
3fc2228f1d | |||
b018819a1f | |||
ac9311d783 | |||
e23ce0f35d | |||
f4b52aa41c | |||
655b040d4d | |||
0f637a5d0f | |||
3f85c327f5 | |||
c2df99072d | |||
e8afbcad9c | |||
e6d8de781b | |||
6e1935899f | |||
169cb85b66 | |||
fe6658d0ac | |||
1f0381228e | |||
f4b63b5de5 | |||
e45a0ad068 | |||
81c6cc021d | |||
859b24aa5b | |||
2bc325f182 | |||
a6186c23e2 | |||
cf234003ec | |||
8d3954304e | |||
9562139fa6 | |||
c857ea9a8f | |||
5c9fa71d95 |
26
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,37 +1,45 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: "[BUG]"
|
title: '[BUG]'
|
||||||
labels: ''
|
labels: ''
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
The Issue tracker is **ONLY** used for reporting bugs. New features should be discussed on our [Slack channel](https://ghostfolio.slack.com) or in [Discussions](https://github.com/ghostfolio/ghostfolio/discussions).
|
The Issue tracker is **ONLY** used for reporting bugs. New features should be discussed on our [Slack channel](https://ghostfolio.slack.com) or in [Discussions](https://github.com/ghostfolio/ghostfolio/discussions).
|
||||||
|
|
||||||
**Describe the bug**
|
**Bug Description**
|
||||||
|
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
**To Reproduce**
|
**To Reproduce**
|
||||||
Steps to reproduce the behavior:
|
|
||||||
|
<!-- Steps to reproduce the behavior -->
|
||||||
|
|
||||||
1.
|
1.
|
||||||
2.
|
2.
|
||||||
3.
|
3.
|
||||||
4.
|
|
||||||
|
|
||||||
**Expected behavior**
|
**Expected behavior**
|
||||||
|
|
||||||
<!-- A clear and concise description of what you expected to happen. -->
|
<!-- A clear and concise description of what you expected to happen. -->
|
||||||
|
|
||||||
**Screenshots**
|
**Screenshots**
|
||||||
|
|
||||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||||
|
|
||||||
**Logs**
|
**Logs**
|
||||||
|
|
||||||
<!-- If applicable, add logs to help explain your problem. -->
|
<!-- If applicable, add logs to help explain your problem. -->
|
||||||
|
|
||||||
**Environment (please complete the following information):**
|
**Environment**
|
||||||
- Ghostfolio Version [e.g. 1.194.0]
|
|
||||||
- Browser [e.g. chrome]
|
<!-- Please complete the following information -->
|
||||||
- OS
|
|
||||||
|
- Ghostfolio Version X.Y.Z
|
||||||
|
- Browser
|
||||||
|
- OS
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
|
|
||||||
<!-- Add any other context about the problem here. -->
|
<!-- Add any other context about the problem here. -->
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
{
|
{
|
||||||
|
"attributeGroups": [
|
||||||
|
"$ANGULAR_ELEMENT_REF",
|
||||||
|
"$ANGULAR_STRUCTURAL_DIRECTIVE",
|
||||||
|
"$DEFAULT",
|
||||||
|
"$ANGULAR_INPUT",
|
||||||
|
"$ANGULAR_TWO_WAY_BINDING",
|
||||||
|
"$ANGULAR_OUTPUT"
|
||||||
|
],
|
||||||
|
"attributeSort": "ASC",
|
||||||
"endOfLine": "auto",
|
"endOfLine": "auto",
|
||||||
"printWidth": 80,
|
"printWidth": 80,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
|
126
CHANGELOG.md
@ -5,7 +5,127 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## 1.199.0 - 27.09.2022
|
## 1.207.0 - 31.10.2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for translated labels of asset and asset sub class
|
||||||
|
- Added support for dates in _ISO 8601_ date format (`YYYY-MM-DD`) in the activities import
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Darkened the background color of the dark mode
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed the public page
|
||||||
|
- Improved the loading indicator of the portfolio evolution chart
|
||||||
|
|
||||||
|
## 1.206.2 - 20.10.2022
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Fixed the `rxjs` version to `7.5.6` (resolutions)
|
||||||
|
- Migrated the `angular.json` to `project.json` files in the `Nx` workspace
|
||||||
|
- Upgraded `nestjs` from version `9.0.7` to `9.1.4`
|
||||||
|
- Upgraded `Nx` from version `14.6.4` to `15.0.0`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed the performance calculation including `SELL` activities with a significant performance gain
|
||||||
|
|
||||||
|
## 1.205.2 - 16.10.2022
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Persisted the language on url change
|
||||||
|
- Improved the portfolio evolution chart
|
||||||
|
- Refactored the appearance (dark mode) in user settings (from `appearance` to `colorScheme`)
|
||||||
|
- Improved the wording on the landing page
|
||||||
|
|
||||||
|
## 1.204.1 - 15.10.2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support to change the appearance (dark mode) in user settings
|
||||||
|
- Added the total amount chart to the investment timeline
|
||||||
|
- Setup the `prettier` plugin `prettier-plugin-organize-attributes`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Respected the current date in the _FIRE_ calculator
|
||||||
|
- Simplified the settings management in the admin control panel
|
||||||
|
- Renamed the data source type `RAKUTEN` to `RAPID_API`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed some links in the blog posts
|
||||||
|
- Fixed the alignment of the value component on the allocations page
|
||||||
|
|
||||||
|
### Todo
|
||||||
|
|
||||||
|
- Rename the environment variable from `RAKUTEN_RAPID_API_KEY` to `RAPID_API_API_KEY`
|
||||||
|
|
||||||
|
## 1.203.0 - 08.10.2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Supported a progressive line animation in the line chart component
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Moved the benchmark comparator from experimental to general availability
|
||||||
|
- Improved the user interface of the benchmark comparator
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed an issue in the performance and chart calculation of today
|
||||||
|
- Fixed the alignment of the value component in the admin control panel
|
||||||
|
|
||||||
|
## 1.202.0 - 07.10.2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for a translated 4% rule in the _FIRE_ section
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improved the caching of the benchmarks in the markets overview (only cache if fetching was successful)
|
||||||
|
- Improved the wording in the twitter bot service
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed the support for cryptocurrencies having a symbol with less than 3 characters (e.g. `SC-USD`)
|
||||||
|
- Fixed the text truncation in the value component
|
||||||
|
|
||||||
|
## 1.201.0 - 01.10.2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added a blog post: _Hacktoberfest 2022_
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improved the usage of the value component in the admin control panel
|
||||||
|
- Improved the language localization for Español (`es`)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed the usage of the value component on the allocations page
|
||||||
|
|
||||||
|
## 1.200.0 - 01.10.2022
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added a mini statistics section to the landing page including pulls on _Docker Hub_
|
||||||
|
- Added an _As seen in_ section to the landing page
|
||||||
|
- Added support for an icon in the value component
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Upgraded `prisma` from version `4.1.1` to `4.4.0`
|
||||||
|
|
||||||
|
## 1.199.1 - 27.09.2022
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@ -890,8 +1010,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Display the value in base currency in the accounts table on mobile
|
- Displayed the value in base currency in the accounts table on mobile
|
||||||
- Display the value in base currency in the activities table on mobile
|
- Displayed the value in base currency in the activities table on mobile
|
||||||
- Renamed `orders` to `activities` in import and export functionality
|
- Renamed `orders` to `activities` in import and export functionality
|
||||||
- Harmonized the algebraic sign of `currentGrossPerformancePercent` and `currentNetPerformancePercent` with `currentGrossPerformance` and `currentNetPerformance`
|
- Harmonized the algebraic sign of `currentGrossPerformancePercent` and `currentNetPerformancePercent` with `currentGrossPerformance` and `currentNetPerformance`
|
||||||
- Improved the pricing page
|
- Improved the pricing page
|
||||||
|
@ -25,7 +25,6 @@ RUN yarn install
|
|||||||
COPY ./decorate-angular-cli.js decorate-angular-cli.js
|
COPY ./decorate-angular-cli.js decorate-angular-cli.js
|
||||||
RUN node decorate-angular-cli.js
|
RUN node decorate-angular-cli.js
|
||||||
|
|
||||||
COPY ./angular.json angular.json
|
|
||||||
COPY ./nx.json nx.json
|
COPY ./nx.json nx.json
|
||||||
COPY ./replace.build.js replace.build.js
|
COPY ./replace.build.js replace.build.js
|
||||||
COPY ./jest.preset.js jest.preset.js
|
COPY ./jest.preset.js jest.preset.js
|
||||||
|
18
README.md
@ -190,20 +190,22 @@ Run `yarn test`
|
|||||||
|
|
||||||
## Public API
|
## Public API
|
||||||
|
|
||||||
|
### Authorization: Bearer Token
|
||||||
|
|
||||||
|
Set the header for each request as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
"Authorization": "Bearer eyJh..."
|
||||||
|
```
|
||||||
|
|
||||||
|
You can get the _Bearer Token_ via `GET http://localhost:3333/api/v1/auth/anonymous/<INSERT_SECURITY_TOKEN_OF_ACCOUNT>` or `curl -s http://localhost:3333/api/v1/auth/anonymous/<INSERT_SECURITY_TOKEN_OF_ACCOUNT>`.
|
||||||
|
|
||||||
### Import Activities
|
### Import Activities
|
||||||
|
|
||||||
#### Request
|
#### Request
|
||||||
|
|
||||||
`POST http://localhost:3333/api/v1/import`
|
`POST http://localhost:3333/api/v1/import`
|
||||||
|
|
||||||
#### Authorization: Bearer Token
|
|
||||||
|
|
||||||
Set the header as follows:
|
|
||||||
|
|
||||||
```
|
|
||||||
"Authorization": "Bearer eyJh..."
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Body
|
#### Body
|
||||||
|
|
||||||
```
|
```
|
||||||
|
395
angular.json
@ -1,395 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"projects": {
|
|
||||||
"api": {
|
|
||||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
||||||
"root": "apps/api",
|
|
||||||
"sourceRoot": "apps/api/src",
|
|
||||||
"projectType": "application",
|
|
||||||
"prefix": "api",
|
|
||||||
"schematics": {},
|
|
||||||
"architect": {
|
|
||||||
"build": {
|
|
||||||
"builder": "@nrwl/node:webpack",
|
|
||||||
"options": {
|
|
||||||
"outputPath": "dist/apps/api",
|
|
||||||
"main": "apps/api/src/main.ts",
|
|
||||||
"tsConfig": "apps/api/tsconfig.app.json",
|
|
||||||
"assets": ["apps/api/src/assets"]
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"generatePackageJson": true,
|
|
||||||
"optimization": true,
|
|
||||||
"extractLicenses": true,
|
|
||||||
"inspect": false,
|
|
||||||
"fileReplacements": [
|
|
||||||
{
|
|
||||||
"replace": "apps/api/src/environments/environment.ts",
|
|
||||||
"with": "apps/api/src/environments/environment.prod.ts"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"outputs": ["{options.outputPath}"]
|
|
||||||
},
|
|
||||||
"serve": {
|
|
||||||
"builder": "@nrwl/node:node",
|
|
||||||
"options": {
|
|
||||||
"buildTarget": "api:build"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"builder": "@nrwl/linter:eslint",
|
|
||||||
"options": {
|
|
||||||
"lintFilePatterns": ["apps/api/**/*.ts"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@nrwl/jest:jest",
|
|
||||||
"options": {
|
|
||||||
"jestConfig": "apps/api/jest.config.ts",
|
|
||||||
"passWithNoTests": true
|
|
||||||
},
|
|
||||||
"outputs": ["coverage/apps/api"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": []
|
|
||||||
},
|
|
||||||
"client": {
|
|
||||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
||||||
"projectType": "application",
|
|
||||||
"schematics": {
|
|
||||||
"@schematics/angular:component": {
|
|
||||||
"style": "scss"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "apps/client",
|
|
||||||
"sourceRoot": "apps/client/src",
|
|
||||||
"prefix": "gf",
|
|
||||||
"architect": {
|
|
||||||
"build": {
|
|
||||||
"builder": "@angular-devkit/build-angular:browser",
|
|
||||||
"options": {
|
|
||||||
"outputPath": "dist/apps/client",
|
|
||||||
"index": "apps/client/src/index.html",
|
|
||||||
"main": "apps/client/src/main.ts",
|
|
||||||
"polyfills": "apps/client/src/polyfills.ts",
|
|
||||||
"tsConfig": "apps/client/tsconfig.app.json",
|
|
||||||
"assets": [
|
|
||||||
{
|
|
||||||
"glob": "assetlinks.json",
|
|
||||||
"input": "apps/client/src/assets",
|
|
||||||
"output": "./../.well-known"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"glob": "CHANGELOG.md",
|
|
||||||
"input": "",
|
|
||||||
"output": "./../assets"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"glob": "LICENSE",
|
|
||||||
"input": "",
|
|
||||||
"output": "./../assets"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"glob": "robots.txt",
|
|
||||||
"input": "apps/client/src/assets",
|
|
||||||
"output": "./../"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"glob": "sitemap.xml",
|
|
||||||
"input": "apps/client/src/assets",
|
|
||||||
"output": "./../"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"glob": "**/*",
|
|
||||||
"input": "node_modules/ionicons/dist/ionicons",
|
|
||||||
"output": "./../ionicons"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"glob": "**/*.js",
|
|
||||||
"input": "node_modules/ionicons/dist/",
|
|
||||||
"output": "./../"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"glob": "**/*",
|
|
||||||
"input": "apps/client/src/assets",
|
|
||||||
"output": "./../assets/"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"styles": ["apps/client/src/styles.scss"],
|
|
||||||
"scripts": ["node_modules/marked/marked.min.js"],
|
|
||||||
"vendorChunk": true,
|
|
||||||
"extractLicenses": false,
|
|
||||||
"buildOptimizer": false,
|
|
||||||
"sourceMap": true,
|
|
||||||
"optimization": false,
|
|
||||||
"namedChunks": true
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"development-de": {
|
|
||||||
"baseHref": "/de/",
|
|
||||||
"localize": ["de"]
|
|
||||||
},
|
|
||||||
"development-en": {
|
|
||||||
"baseHref": "/en/",
|
|
||||||
"localize": ["en"]
|
|
||||||
},
|
|
||||||
"development-es": {
|
|
||||||
"baseHref": "/es/",
|
|
||||||
"localize": ["es"]
|
|
||||||
},
|
|
||||||
"development-it": {
|
|
||||||
"baseHref": "/it/",
|
|
||||||
"localize": ["it"]
|
|
||||||
},
|
|
||||||
"development-nl": {
|
|
||||||
"baseHref": "/nl/",
|
|
||||||
"localize": ["nl"]
|
|
||||||
},
|
|
||||||
"production": {
|
|
||||||
"fileReplacements": [
|
|
||||||
{
|
|
||||||
"replace": "apps/client/src/environments/environment.ts",
|
|
||||||
"with": "apps/client/src/environments/environment.prod.ts"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"optimization": true,
|
|
||||||
"outputHashing": "all",
|
|
||||||
"sourceMap": false,
|
|
||||||
"namedChunks": false,
|
|
||||||
"extractLicenses": true,
|
|
||||||
"vendorChunk": false,
|
|
||||||
"buildOptimizer": true,
|
|
||||||
"budgets": [
|
|
||||||
{
|
|
||||||
"type": "initial",
|
|
||||||
"maximumWarning": "2mb",
|
|
||||||
"maximumError": "5mb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "anyComponentStyle",
|
|
||||||
"maximumWarning": "6kb",
|
|
||||||
"maximumError": "10kb"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"outputs": ["{options.outputPath}"],
|
|
||||||
"defaultConfiguration": ""
|
|
||||||
},
|
|
||||||
"serve": {
|
|
||||||
"builder": "@angular-devkit/build-angular:dev-server",
|
|
||||||
"options": {
|
|
||||||
"browserTarget": "client:build",
|
|
||||||
"proxyConfig": "apps/client/proxy.conf.json"
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"development-de": {
|
|
||||||
"browserTarget": "client:build:development-de"
|
|
||||||
},
|
|
||||||
"development-en": {
|
|
||||||
"browserTarget": "client:build:development-en"
|
|
||||||
},
|
|
||||||
"development-es": {
|
|
||||||
"browserTarget": "client:build:development-es"
|
|
||||||
},
|
|
||||||
"development-it": {
|
|
||||||
"browserTarget": "client:build:development-it"
|
|
||||||
},
|
|
||||||
"development-nl": {
|
|
||||||
"browserTarget": "client:build:development-nl"
|
|
||||||
},
|
|
||||||
"production": {
|
|
||||||
"browserTarget": "client:build:production"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"extract-i18n": {
|
|
||||||
"builder": "ng-extract-i18n-merge:ng-extract-i18n-merge",
|
|
||||||
"options": {
|
|
||||||
"browserTarget": "client:build",
|
|
||||||
"includeContext": true,
|
|
||||||
"outputPath": "src/locales",
|
|
||||||
"targetFiles": [
|
|
||||||
"messages.de.xlf",
|
|
||||||
"messages.es.xlf",
|
|
||||||
"messages.it.xlf",
|
|
||||||
"messages.nl.xlf"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"builder": "@nrwl/linter:eslint",
|
|
||||||
"options": {
|
|
||||||
"lintFilePatterns": ["apps/client/**/*.ts"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@nrwl/jest:jest",
|
|
||||||
"options": {
|
|
||||||
"jestConfig": "apps/client/jest.config.ts",
|
|
||||||
"passWithNoTests": true
|
|
||||||
},
|
|
||||||
"outputs": ["coverage/apps/client"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"i18n": {
|
|
||||||
"locales": {
|
|
||||||
"de": {
|
|
||||||
"baseHref": "/de/",
|
|
||||||
"translation": "apps/client/src/locales/messages.de.xlf"
|
|
||||||
},
|
|
||||||
"es": {
|
|
||||||
"baseHref": "/es/",
|
|
||||||
"translation": "apps/client/src/locales/messages.es.xlf"
|
|
||||||
},
|
|
||||||
"it": {
|
|
||||||
"baseHref": "/it/",
|
|
||||||
"translation": "apps/client/src/locales/messages.it.xlf"
|
|
||||||
},
|
|
||||||
"nl": {
|
|
||||||
"baseHref": "/nl/",
|
|
||||||
"translation": "apps/client/src/locales/messages.nl.xlf"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourceLocale": "en"
|
|
||||||
},
|
|
||||||
"tags": []
|
|
||||||
},
|
|
||||||
"client-e2e": {
|
|
||||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
||||||
"root": "apps/client-e2e",
|
|
||||||
"sourceRoot": "apps/client-e2e/src",
|
|
||||||
"projectType": "application",
|
|
||||||
"architect": {
|
|
||||||
"e2e": {
|
|
||||||
"builder": "@nrwl/cypress:cypress",
|
|
||||||
"options": {
|
|
||||||
"cypressConfig": "apps/client-e2e/cypress.json",
|
|
||||||
"tsConfig": "apps/client-e2e/tsconfig.e2e.json",
|
|
||||||
"devServerTarget": "client:serve"
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"production": {
|
|
||||||
"devServerTarget": "client:serve:production"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [],
|
|
||||||
"implicitDependencies": ["client"]
|
|
||||||
},
|
|
||||||
"common": {
|
|
||||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
||||||
"root": "libs/common",
|
|
||||||
"sourceRoot": "libs/common/src",
|
|
||||||
"projectType": "library",
|
|
||||||
"architect": {
|
|
||||||
"lint": {
|
|
||||||
"builder": "@nrwl/linter:eslint",
|
|
||||||
"options": {
|
|
||||||
"lintFilePatterns": ["libs/common/**/*.ts"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"test": {
|
|
||||||
"builder": "@nrwl/jest:jest",
|
|
||||||
"outputs": ["coverage/libs/common"],
|
|
||||||
"options": {
|
|
||||||
"jestConfig": "libs/common/jest.config.ts",
|
|
||||||
"passWithNoTests": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": []
|
|
||||||
},
|
|
||||||
"ui": {
|
|
||||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
||||||
"projectType": "library",
|
|
||||||
"schematics": {
|
|
||||||
"@schematics/angular:component": {
|
|
||||||
"style": "scss"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "libs/ui",
|
|
||||||
"sourceRoot": "libs/ui/src",
|
|
||||||
"prefix": "gf",
|
|
||||||
"architect": {
|
|
||||||
"test": {
|
|
||||||
"builder": "@nrwl/jest:jest",
|
|
||||||
"outputs": ["coverage/libs/ui"],
|
|
||||||
"options": {
|
|
||||||
"jestConfig": "libs/ui/jest.config.ts",
|
|
||||||
"passWithNoTests": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"builder": "@nrwl/linter:eslint",
|
|
||||||
"options": {
|
|
||||||
"lintFilePatterns": ["libs/ui/src/**/*.ts", "libs/ui/src/**/*.html"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"storybook": {
|
|
||||||
"builder": "@storybook/angular:start-storybook",
|
|
||||||
"options": {
|
|
||||||
"port": 4400,
|
|
||||||
"configDir": "libs/ui/.storybook",
|
|
||||||
"browserTarget": "ui:build-storybook",
|
|
||||||
"compodoc": false
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"ci": {
|
|
||||||
"quiet": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"build-storybook": {
|
|
||||||
"builder": "@storybook/angular:build-storybook",
|
|
||||||
"outputs": ["{options.outputPath}"],
|
|
||||||
"options": {
|
|
||||||
"outputDir": "dist/storybook/ui",
|
|
||||||
"configDir": "libs/ui/.storybook",
|
|
||||||
"browserTarget": "ui:build-storybook",
|
|
||||||
"compodoc": false
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"ci": {
|
|
||||||
"quiet": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": []
|
|
||||||
},
|
|
||||||
"ui-e2e": {
|
|
||||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
||||||
"root": "apps/ui-e2e",
|
|
||||||
"sourceRoot": "apps/ui-e2e/src",
|
|
||||||
"projectType": "application",
|
|
||||||
"architect": {
|
|
||||||
"e2e": {
|
|
||||||
"builder": "@nrwl/cypress:cypress",
|
|
||||||
"options": {
|
|
||||||
"cypressConfig": "apps/ui-e2e/cypress.json",
|
|
||||||
"devServerTarget": "ui:storybook",
|
|
||||||
"tsConfig": "apps/ui-e2e/tsconfig.json"
|
|
||||||
},
|
|
||||||
"configurations": {
|
|
||||||
"ci": {
|
|
||||||
"devServerTarget": "ui:storybook:ci"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint": {
|
|
||||||
"builder": "@nrwl/linter:eslint",
|
|
||||||
"options": {
|
|
||||||
"lintFilePatterns": ["apps/ui-e2e/**/*.{js,ts}"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [],
|
|
||||||
"implicitDependencies": ["ui"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
56
apps/api/project.json
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "apps/api/src",
|
||||||
|
"projectType": "application",
|
||||||
|
"prefix": "api",
|
||||||
|
"generators": {},
|
||||||
|
"targets": {
|
||||||
|
"build": {
|
||||||
|
"executor": "@nrwl/webpack:webpack",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"generatePackageJson": true,
|
||||||
|
"optimization": true,
|
||||||
|
"extractLicenses": true,
|
||||||
|
"inspect": false,
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "apps/api/src/environments/environment.ts",
|
||||||
|
"with": "apps/api/src/environments/environment.prod.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": ["{options.outputPath}"]
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"executor": "@nrwl/node:node",
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "api:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nrwl/linter:eslint",
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": ["apps/api/**/*.ts"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"executor": "@nrwl/jest:jest",
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "apps/api/jest.config.ts",
|
||||||
|
"passWithNoTests": true
|
||||||
|
},
|
||||||
|
"outputs": ["{workspaceRoot}/coverage/apps/api"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -95,11 +95,10 @@ export class AccountController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let accountsWithAggregations =
|
let accountsWithAggregations =
|
||||||
await this.portfolioService.getAccountsWithAggregations(
|
await this.portfolioService.getAccountsWithAggregations({
|
||||||
impersonationUserId || this.request.user.id,
|
userId: impersonationUserId || this.request.user.id,
|
||||||
undefined,
|
withExcludedAccounts: true
|
||||||
true
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
impersonationUserId ||
|
impersonationUserId ||
|
||||||
@ -139,11 +138,11 @@ export class AccountController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let accountsWithAggregations =
|
let accountsWithAggregations =
|
||||||
await this.portfolioService.getAccountsWithAggregations(
|
await this.portfolioService.getAccountsWithAggregations({
|
||||||
impersonationUserId || this.request.user.id,
|
filters: [{ id, type: 'ACCOUNT' }],
|
||||||
[{ id, type: 'ACCOUNT' }],
|
userId: impersonationUserId || this.request.user.id,
|
||||||
true
|
withExcludedAccounts: true
|
||||||
);
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
impersonationUserId ||
|
impersonationUserId ||
|
||||||
|
@ -181,10 +181,10 @@ export class AdminService {
|
|||||||
public async putSetting(key: string, value: string) {
|
public async putSetting(key: string, value: string) {
|
||||||
let response: Property;
|
let response: Property;
|
||||||
|
|
||||||
if (value === '') {
|
if (value) {
|
||||||
response = await this.propertyService.delete({ key });
|
|
||||||
} else {
|
|
||||||
response = await this.propertyService.put({ key, value });
|
response = await this.propertyService.put({ key, value });
|
||||||
|
} else {
|
||||||
|
response = await this.propertyService.delete({ key });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === PROPERTY_CURRENCIES) {
|
if (key === PROPERTY_CURRENCIES) {
|
||||||
|
@ -73,6 +73,7 @@ export class BenchmarkService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const allTimeHighs = await Promise.all(promises);
|
const allTimeHighs = await Promise.all(promises);
|
||||||
|
let storeInCache = true;
|
||||||
|
|
||||||
benchmarks = allTimeHighs.map((allTimeHigh, index) => {
|
benchmarks = allTimeHighs.map((allTimeHigh, index) => {
|
||||||
const { marketPrice } =
|
const { marketPrice } =
|
||||||
@ -85,6 +86,8 @@ export class BenchmarkService {
|
|||||||
allTimeHigh,
|
allTimeHigh,
|
||||||
marketPrice
|
marketPrice
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
storeInCache = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -100,11 +103,13 @@ export class BenchmarkService {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.redisCacheService.set(
|
if (storeInCache) {
|
||||||
this.CACHE_KEY_BENCHMARKS,
|
await this.redisCacheService.set(
|
||||||
JSON.stringify(benchmarks),
|
this.CACHE_KEY_BENCHMARKS,
|
||||||
ms('4 hours') / 1000
|
JSON.stringify(benchmarks),
|
||||||
);
|
ms('4 hours') / 1000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return benchmarks;
|
return benchmarks;
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,11 @@ export class FrontendMiddleware implements NestMiddleware {
|
|||||||
req.path === '/en/blog/2022/08/500-stars-on-github/'
|
req.path === '/en/blog/2022/08/500-stars-on-github/'
|
||||||
) {
|
) {
|
||||||
featureGraphicPath = 'assets/images/blog/500-stars-on-github.jpg';
|
featureGraphicPath = 'assets/images/blog/500-stars-on-github.jpg';
|
||||||
|
} else if (
|
||||||
|
req.path === '/en/blog/2022/10/hacktoberfest-2022' ||
|
||||||
|
req.path === '/en/blog/2022/10/hacktoberfest-2022/'
|
||||||
|
) {
|
||||||
|
featureGraphicPath = 'assets/images/blog/hacktoberfest-2022.png';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -78,7 +83,7 @@ export class FrontendMiddleware implements NestMiddleware {
|
|||||||
);
|
);
|
||||||
} else if (req.path === '/es' || req.path.startsWith('/es/')) {
|
} else if (req.path === '/es' || req.path.startsWith('/es/')) {
|
||||||
res.send(
|
res.send(
|
||||||
this.interpolate(this.indexHtmlIt, {
|
this.interpolate(this.indexHtmlEs, {
|
||||||
featureGraphicPath,
|
featureGraphicPath,
|
||||||
languageCode: 'es',
|
languageCode: 'es',
|
||||||
path: req.path,
|
path: req.path,
|
||||||
|
@ -145,6 +145,27 @@ export class InfoService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async countDockerHubPulls(): Promise<number> {
|
||||||
|
try {
|
||||||
|
const get = bent(
|
||||||
|
`https://hub.docker.com/v2/repositories/ghostfolio/ghostfolio`,
|
||||||
|
'GET',
|
||||||
|
'json',
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
'User-Agent': 'request'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { pull_count } = await get();
|
||||||
|
return pull_count;
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(error, 'InfoService');
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async countGitHubContributors(): Promise<number> {
|
private async countGitHubContributors(): Promise<number> {
|
||||||
try {
|
try {
|
||||||
const get = bent(
|
const get = bent(
|
||||||
@ -245,6 +266,8 @@ export class InfoService {
|
|||||||
const activeUsers1d = await this.countActiveUsers(1);
|
const activeUsers1d = await this.countActiveUsers(1);
|
||||||
const activeUsers30d = await this.countActiveUsers(30);
|
const activeUsers30d = await this.countActiveUsers(30);
|
||||||
const newUsers30d = await this.countNewUsers(30);
|
const newUsers30d = await this.countNewUsers(30);
|
||||||
|
|
||||||
|
const dockerHubPulls = await this.countDockerHubPulls();
|
||||||
const gitHubContributors = await this.countGitHubContributors();
|
const gitHubContributors = await this.countGitHubContributors();
|
||||||
const gitHubStargazers = await this.countGitHubStargazers();
|
const gitHubStargazers = await this.countGitHubStargazers();
|
||||||
const slackCommunityUsers = await this.countSlackCommunityUsers();
|
const slackCommunityUsers = await this.countSlackCommunityUsers();
|
||||||
@ -252,6 +275,7 @@ export class InfoService {
|
|||||||
statistics = {
|
statistics = {
|
||||||
activeUsers1d,
|
activeUsers1d,
|
||||||
activeUsers30d,
|
activeUsers30d,
|
||||||
|
dockerHubPulls,
|
||||||
gitHubContributors,
|
gitHubContributors,
|
||||||
gitHubStargazers,
|
gitHubStargazers,
|
||||||
newUsers30d,
|
newUsers30d,
|
||||||
|
@ -3,8 +3,8 @@ import { nullifyValuesInObjects } from '@ghostfolio/api/helper/object.helper';
|
|||||||
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor';
|
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor';
|
||||||
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
|
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
|
||||||
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
|
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
|
||||||
|
import { ApiService } from '@ghostfolio/api/services/api/api.service';
|
||||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
||||||
import { Filter } from '@ghostfolio/common/interfaces';
|
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import type { RequestWithUser } from '@ghostfolio/common/types';
|
import type { RequestWithUser } from '@ghostfolio/common/types';
|
||||||
import {
|
import {
|
||||||
@ -36,6 +36,7 @@ import { UpdateOrderDto } from './update-order.dto';
|
|||||||
@Controller('order')
|
@Controller('order')
|
||||||
export class OrderController {
|
export class OrderController {
|
||||||
public constructor(
|
public constructor(
|
||||||
|
private readonly apiService: ApiService,
|
||||||
private readonly impersonationService: ImpersonationService,
|
private readonly impersonationService: ImpersonationService,
|
||||||
private readonly orderService: OrderService,
|
private readonly orderService: OrderService,
|
||||||
@Inject(REQUEST) private readonly request: RequestWithUser,
|
@Inject(REQUEST) private readonly request: RequestWithUser,
|
||||||
@ -73,30 +74,11 @@ export class OrderController {
|
|||||||
@Query('assetClasses') filterByAssetClasses?: string,
|
@Query('assetClasses') filterByAssetClasses?: string,
|
||||||
@Query('tags') filterByTags?: string
|
@Query('tags') filterByTags?: string
|
||||||
): Promise<Activities> {
|
): Promise<Activities> {
|
||||||
const accountIds = filterByAccounts?.split(',') ?? [];
|
const filters = this.apiService.buildFiltersFromQueryParams({
|
||||||
const assetClasses = filterByAssetClasses?.split(',') ?? [];
|
filterByAccounts,
|
||||||
const tagIds = filterByTags?.split(',') ?? [];
|
filterByAssetClasses,
|
||||||
|
filterByTags
|
||||||
const filters: Filter[] = [
|
});
|
||||||
...accountIds.map((accountId) => {
|
|
||||||
return <Filter>{
|
|
||||||
id: accountId,
|
|
||||||
type: 'ACCOUNT'
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
...assetClasses.map((assetClass) => {
|
|
||||||
return <Filter>{
|
|
||||||
id: assetClass,
|
|
||||||
type: 'ASSET_CLASS'
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
...tagIds.map((tagId) => {
|
|
||||||
return <Filter>{
|
|
||||||
id: tagId,
|
|
||||||
type: 'TAG'
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
const impersonationUserId =
|
const impersonationUserId =
|
||||||
await this.impersonationService.validateImpersonationId(
|
await this.impersonationService.validateImpersonationId(
|
||||||
|
@ -2,6 +2,7 @@ import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
|||||||
import { CacheModule } from '@ghostfolio/api/app/cache/cache.module';
|
import { CacheModule } from '@ghostfolio/api/app/cache/cache.module';
|
||||||
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
||||||
import { UserModule } from '@ghostfolio/api/app/user/user.module';
|
import { UserModule } from '@ghostfolio/api/app/user/user.module';
|
||||||
|
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
|
||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
||||||
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
|
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
|
||||||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||||
@ -18,6 +19,7 @@ import { OrderService } from './order.service';
|
|||||||
controllers: [OrderController],
|
controllers: [OrderController],
|
||||||
exports: [OrderService],
|
exports: [OrderService],
|
||||||
imports: [
|
imports: [
|
||||||
|
ApiModule,
|
||||||
CacheModule,
|
CacheModule,
|
||||||
ConfigurationModule,
|
ConfigurationModule,
|
||||||
DataGatheringModule,
|
DataGatheringModule,
|
||||||
|
@ -21,6 +21,17 @@ function mockGetValue(symbol: string, date: Date) {
|
|||||||
|
|
||||||
return { marketPrice: 0 };
|
return { marketPrice: 0 };
|
||||||
|
|
||||||
|
case 'BTCUSD':
|
||||||
|
if (isSameDay(parseDate('2015-01-01'), date)) {
|
||||||
|
return { marketPrice: 314.25 };
|
||||||
|
} else if (isSameDay(parseDate('2017-12-31'), date)) {
|
||||||
|
return { marketPrice: 14156.4 };
|
||||||
|
} else if (isSameDay(parseDate('2018-01-01'), date)) {
|
||||||
|
return { marketPrice: 13657.2 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { marketPrice: 0 };
|
||||||
|
|
||||||
case 'NOVN.SW':
|
case 'NOVN.SW':
|
||||||
if (isSameDay(parseDate('2022-04-11'), date)) {
|
if (isSameDay(parseDate('2022-04-11'), date)) {
|
||||||
return { marketPrice: 87.8 };
|
return { marketPrice: 87.8 };
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
|
import { parseDate } from '@ghostfolio/common/helper';
|
||||||
|
import Big from 'big.js';
|
||||||
|
|
||||||
|
import { CurrentRateServiceMock } from './current-rate.service.mock';
|
||||||
|
import { PortfolioCalculator } from './portfolio-calculator';
|
||||||
|
|
||||||
|
jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
CurrentRateService: jest.fn().mockImplementation(() => {
|
||||||
|
return CurrentRateServiceMock;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PortfolioCalculator', () => {
|
||||||
|
let currentRateService: CurrentRateService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
currentRateService = new CurrentRateService(null, null, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get current positions', () => {
|
||||||
|
it.only('with BTCUSD buy and sell partially', async () => {
|
||||||
|
const portfolioCalculator = new PortfolioCalculator({
|
||||||
|
currentRateService,
|
||||||
|
currency: 'CHF',
|
||||||
|
orders: [
|
||||||
|
{
|
||||||
|
currency: 'CHF',
|
||||||
|
date: '2015-01-01',
|
||||||
|
dataSource: 'YAHOO',
|
||||||
|
fee: new Big(0),
|
||||||
|
name: 'Bitcoin USD',
|
||||||
|
quantity: new Big(2),
|
||||||
|
symbol: 'BTCUSD',
|
||||||
|
type: 'BUY',
|
||||||
|
unitPrice: new Big(320.43)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
currency: 'CHF',
|
||||||
|
date: '2017-12-31',
|
||||||
|
dataSource: 'YAHOO',
|
||||||
|
fee: new Big(0),
|
||||||
|
name: 'Bitcoin USD',
|
||||||
|
quantity: new Big(1),
|
||||||
|
symbol: 'BTCUSD',
|
||||||
|
type: 'SELL',
|
||||||
|
unitPrice: new Big(14156.4)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
portfolioCalculator.computeTransactionPoints();
|
||||||
|
|
||||||
|
const spy = jest
|
||||||
|
.spyOn(Date, 'now')
|
||||||
|
.mockImplementation(() => parseDate('2018-01-01').getTime());
|
||||||
|
|
||||||
|
const currentPositions = await portfolioCalculator.getCurrentPositions(
|
||||||
|
parseDate('2015-01-01')
|
||||||
|
);
|
||||||
|
|
||||||
|
const investments = portfolioCalculator.getInvestments();
|
||||||
|
|
||||||
|
const investmentsByMonth = portfolioCalculator.getInvestmentsByMonth();
|
||||||
|
|
||||||
|
spy.mockRestore();
|
||||||
|
|
||||||
|
expect(currentPositions).toEqual({
|
||||||
|
currentValue: new Big('13657.2'),
|
||||||
|
errors: [],
|
||||||
|
grossPerformance: new Big('27172.74'),
|
||||||
|
grossPerformancePercentage: new Big('42.40043067128546016291'),
|
||||||
|
hasErrors: false,
|
||||||
|
netPerformance: new Big('27172.74'),
|
||||||
|
netPerformancePercentage: new Big('42.40043067128546016291'),
|
||||||
|
positions: [
|
||||||
|
{
|
||||||
|
averagePrice: new Big('320.43'),
|
||||||
|
currency: 'CHF',
|
||||||
|
dataSource: 'YAHOO',
|
||||||
|
firstBuyDate: '2015-01-01',
|
||||||
|
grossPerformance: new Big('27172.74'),
|
||||||
|
grossPerformancePercentage: new Big('42.40043067128546016291'),
|
||||||
|
investment: new Big('320.43'),
|
||||||
|
netPerformance: new Big('27172.74'),
|
||||||
|
netPerformancePercentage: new Big('42.40043067128546016291'),
|
||||||
|
marketPrice: 13657.2,
|
||||||
|
quantity: new Big('1'),
|
||||||
|
symbol: 'BTCUSD',
|
||||||
|
transactionCount: 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
totalInvestment: new Big('320.43')
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(investments).toEqual([
|
||||||
|
{ date: '2015-01-01', investment: new Big('640.86') },
|
||||||
|
{ date: '2017-12-31', investment: new Big('320.43') }
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(investmentsByMonth).toEqual([
|
||||||
|
{ date: '2015-01-01', investment: new Big('640.86') },
|
||||||
|
{ date: '2017-12-01', investment: new Big('-14156.4') }
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -22,7 +22,7 @@ describe('PortfolioCalculator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('get current positions', () => {
|
describe('get current positions', () => {
|
||||||
it.only('with BALN.SW buy and sell', async () => {
|
it.only('with NOVN.SW buy and sell partially', async () => {
|
||||||
const portfolioCalculator = new PortfolioCalculator({
|
const portfolioCalculator = new PortfolioCalculator({
|
||||||
currentRateService,
|
currentRateService,
|
||||||
currency: 'CHF',
|
currency: 'CHF',
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
format,
|
format,
|
||||||
isAfter,
|
isAfter,
|
||||||
isBefore,
|
isBefore,
|
||||||
|
isSameDay,
|
||||||
isSameMonth,
|
isSameMonth,
|
||||||
isSameYear,
|
isSameYear,
|
||||||
max,
|
max,
|
||||||
@ -187,7 +188,9 @@ export class PortfolioCalculator {
|
|||||||
day = addDays(day, step);
|
day = addDays(day, step);
|
||||||
}
|
}
|
||||||
|
|
||||||
dates.push(resetHours(end));
|
if (!isSameDay(last(dates), end)) {
|
||||||
|
dates.push(resetHours(end));
|
||||||
|
}
|
||||||
|
|
||||||
for (const item of transactionPointsBeforeEndDate[firstIndex - 1].items) {
|
for (const item of transactionPointsBeforeEndDate[firstIndex - 1].items) {
|
||||||
dataGatheringItems.push({
|
dataGatheringItems.push({
|
||||||
@ -284,7 +287,10 @@ export class PortfolioCalculator {
|
|||||||
date,
|
date,
|
||||||
netPerformanceInPercentage,
|
netPerformanceInPercentage,
|
||||||
netPerformance: totalNetPerformanceValues[date].toNumber(),
|
netPerformance: totalNetPerformanceValues[date].toNumber(),
|
||||||
value: netPerformanceInPercentage
|
totalInvestment: totalInvestmentValues[date].toNumber(),
|
||||||
|
value: totalInvestmentValues[date]
|
||||||
|
.plus(totalNetPerformanceValues[date])
|
||||||
|
.toNumber()
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -894,12 +900,12 @@ export class PortfolioCalculator {
|
|||||||
let investmentAtStartDate: Big;
|
let investmentAtStartDate: Big;
|
||||||
const investmentValues: { [date: string]: Big } = {};
|
const investmentValues: { [date: string]: Big } = {};
|
||||||
let lastAveragePrice = new Big(0);
|
let lastAveragePrice = new Big(0);
|
||||||
let lastTransactionInvestment = new Big(0);
|
// let lastTransactionInvestment = new Big(0);
|
||||||
let lastValueOfInvestmentBeforeTransaction = new Big(0);
|
// let lastValueOfInvestmentBeforeTransaction = new Big(0);
|
||||||
let maxTotalInvestment = new Big(0);
|
let maxTotalInvestment = new Big(0);
|
||||||
const netPerformanceValues: { [date: string]: Big } = {};
|
const netPerformanceValues: { [date: string]: Big } = {};
|
||||||
let timeWeightedGrossPerformancePercentage = new Big(1);
|
// let timeWeightedGrossPerformancePercentage = new Big(1);
|
||||||
let timeWeightedNetPerformancePercentage = new Big(1);
|
// let timeWeightedNetPerformancePercentage = new Big(1);
|
||||||
let totalInvestment = new Big(0);
|
let totalInvestment = new Big(0);
|
||||||
let totalInvestmentWithGrossPerformanceFromSell = new Big(0);
|
let totalInvestmentWithGrossPerformanceFromSell = new Big(0);
|
||||||
let totalUnits = new Big(0);
|
let totalUnits = new Big(0);
|
||||||
@ -994,6 +1000,12 @@ export class PortfolioCalculator {
|
|||||||
for (let i = 0; i < orders.length; i += 1) {
|
for (let i = 0; i < orders.length; i += 1) {
|
||||||
const order = orders[i];
|
const order = orders[i];
|
||||||
|
|
||||||
|
if (PortfolioCalculator.ENABLE_LOGGING) {
|
||||||
|
console.log();
|
||||||
|
console.log();
|
||||||
|
console.log(i + 1, order.type, order.itemType);
|
||||||
|
}
|
||||||
|
|
||||||
if (order.itemType === 'start') {
|
if (order.itemType === 'start') {
|
||||||
// Take the unit price of the order as the market price if there are no
|
// Take the unit price of the order as the market price if there are no
|
||||||
// orders of this symbol before the start date
|
// orders of this symbol before the start date
|
||||||
@ -1021,9 +1033,21 @@ export class PortfolioCalculator {
|
|||||||
valueAtStartDate = valueOfInvestmentBeforeTransaction;
|
valueAtStartDate = valueOfInvestmentBeforeTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
const transactionInvestment = order.quantity
|
const transactionInvestment =
|
||||||
.mul(order.unitPrice)
|
order.type === 'BUY'
|
||||||
.mul(this.getFactor(order.type));
|
? order.quantity.mul(order.unitPrice).mul(this.getFactor(order.type))
|
||||||
|
: totalUnits.gt(0)
|
||||||
|
? totalInvestment
|
||||||
|
.div(totalUnits)
|
||||||
|
.mul(order.quantity)
|
||||||
|
.mul(this.getFactor(order.type))
|
||||||
|
: new Big(0);
|
||||||
|
|
||||||
|
if (PortfolioCalculator.ENABLE_LOGGING) {
|
||||||
|
console.log('totalInvestment', totalInvestment.toNumber());
|
||||||
|
console.log('order.quantity', order.quantity.toNumber());
|
||||||
|
console.log('transactionInvestment', transactionInvestment.toNumber());
|
||||||
|
}
|
||||||
|
|
||||||
totalInvestment = totalInvestment.plus(transactionInvestment);
|
totalInvestment = totalInvestment.plus(transactionInvestment);
|
||||||
|
|
||||||
@ -1072,58 +1096,69 @@ export class PortfolioCalculator {
|
|||||||
? new Big(0)
|
? new Big(0)
|
||||||
: totalInvestmentWithGrossPerformanceFromSell.div(totalUnits);
|
: totalInvestmentWithGrossPerformanceFromSell.div(totalUnits);
|
||||||
|
|
||||||
|
if (PortfolioCalculator.ENABLE_LOGGING) {
|
||||||
|
console.log(
|
||||||
|
'totalInvestmentWithGrossPerformanceFromSell',
|
||||||
|
totalInvestmentWithGrossPerformanceFromSell.toNumber()
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
'grossPerformanceFromSells',
|
||||||
|
grossPerformanceFromSells.toNumber()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const newGrossPerformance = valueOfInvestment
|
const newGrossPerformance = valueOfInvestment
|
||||||
.minus(totalInvestmentWithGrossPerformanceFromSell)
|
.minus(totalInvestment)
|
||||||
.plus(grossPerformanceFromSells);
|
.plus(grossPerformanceFromSells);
|
||||||
|
|
||||||
if (
|
// if (
|
||||||
i > indexOfStartOrder &&
|
// i > indexOfStartOrder &&
|
||||||
!lastValueOfInvestmentBeforeTransaction
|
// !lastValueOfInvestmentBeforeTransaction
|
||||||
.plus(lastTransactionInvestment)
|
// .plus(lastTransactionInvestment)
|
||||||
.eq(0)
|
// .eq(0)
|
||||||
) {
|
// ) {
|
||||||
const grossHoldingPeriodReturn = valueOfInvestmentBeforeTransaction
|
// const grossHoldingPeriodReturn = valueOfInvestmentBeforeTransaction
|
||||||
.minus(
|
// .minus(
|
||||||
lastValueOfInvestmentBeforeTransaction.plus(
|
// lastValueOfInvestmentBeforeTransaction.plus(
|
||||||
lastTransactionInvestment
|
// lastTransactionInvestment
|
||||||
)
|
// )
|
||||||
)
|
// )
|
||||||
.div(
|
// .div(
|
||||||
lastValueOfInvestmentBeforeTransaction.plus(
|
// lastValueOfInvestmentBeforeTransaction.plus(
|
||||||
lastTransactionInvestment
|
// lastTransactionInvestment
|
||||||
)
|
// )
|
||||||
);
|
// );
|
||||||
|
|
||||||
timeWeightedGrossPerformancePercentage =
|
// timeWeightedGrossPerformancePercentage =
|
||||||
timeWeightedGrossPerformancePercentage.mul(
|
// timeWeightedGrossPerformancePercentage.mul(
|
||||||
new Big(1).plus(grossHoldingPeriodReturn)
|
// new Big(1).plus(grossHoldingPeriodReturn)
|
||||||
);
|
// );
|
||||||
|
|
||||||
const netHoldingPeriodReturn = valueOfInvestmentBeforeTransaction
|
// const netHoldingPeriodReturn = valueOfInvestmentBeforeTransaction
|
||||||
.minus(fees.minus(feesAtStartDate))
|
// .minus(fees.minus(feesAtStartDate))
|
||||||
.minus(
|
// .minus(
|
||||||
lastValueOfInvestmentBeforeTransaction.plus(
|
// lastValueOfInvestmentBeforeTransaction.plus(
|
||||||
lastTransactionInvestment
|
// lastTransactionInvestment
|
||||||
)
|
// )
|
||||||
)
|
// )
|
||||||
.div(
|
// .div(
|
||||||
lastValueOfInvestmentBeforeTransaction.plus(
|
// lastValueOfInvestmentBeforeTransaction.plus(
|
||||||
lastTransactionInvestment
|
// lastTransactionInvestment
|
||||||
)
|
// )
|
||||||
);
|
// );
|
||||||
|
|
||||||
timeWeightedNetPerformancePercentage =
|
// timeWeightedNetPerformancePercentage =
|
||||||
timeWeightedNetPerformancePercentage.mul(
|
// timeWeightedNetPerformancePercentage.mul(
|
||||||
new Big(1).plus(netHoldingPeriodReturn)
|
// new Big(1).plus(netHoldingPeriodReturn)
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
grossPerformance = newGrossPerformance;
|
grossPerformance = newGrossPerformance;
|
||||||
|
|
||||||
lastTransactionInvestment = transactionInvestment;
|
// lastTransactionInvestment = transactionInvestment;
|
||||||
|
|
||||||
lastValueOfInvestmentBeforeTransaction =
|
// lastValueOfInvestmentBeforeTransaction =
|
||||||
valueOfInvestmentBeforeTransaction;
|
// valueOfInvestmentBeforeTransaction;
|
||||||
|
|
||||||
if (order.itemType === 'start') {
|
if (order.itemType === 'start') {
|
||||||
feesAtStartDate = fees;
|
feesAtStartDate = fees;
|
||||||
@ -1135,7 +1170,15 @@ export class PortfolioCalculator {
|
|||||||
.minus(grossPerformanceAtStartDate)
|
.minus(grossPerformanceAtStartDate)
|
||||||
.minus(fees.minus(feesAtStartDate));
|
.minus(fees.minus(feesAtStartDate));
|
||||||
|
|
||||||
investmentValues[order.date] = totalInvestment;
|
investmentValues[order.date] = maxTotalInvestment;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PortfolioCalculator.ENABLE_LOGGING) {
|
||||||
|
console.log('totalInvestment', totalInvestment.toNumber());
|
||||||
|
console.log(
|
||||||
|
'totalGrossPerformance',
|
||||||
|
grossPerformance.minus(grossPerformanceAtStartDate).toNumber()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i === indexOfEndOrder) {
|
if (i === indexOfEndOrder) {
|
||||||
@ -1143,11 +1186,11 @@ export class PortfolioCalculator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
timeWeightedGrossPerformancePercentage =
|
// timeWeightedGrossPerformancePercentage =
|
||||||
timeWeightedGrossPerformancePercentage.minus(1);
|
// timeWeightedGrossPerformancePercentage.minus(1);
|
||||||
|
|
||||||
timeWeightedNetPerformancePercentage =
|
// timeWeightedNetPerformancePercentage =
|
||||||
timeWeightedNetPerformancePercentage.minus(1);
|
// timeWeightedNetPerformancePercentage.minus(1);
|
||||||
|
|
||||||
const totalGrossPerformance = grossPerformance.minus(
|
const totalGrossPerformance = grossPerformance.minus(
|
||||||
grossPerformanceAtStartDate
|
grossPerformanceAtStartDate
|
||||||
|
@ -7,18 +7,15 @@ import {
|
|||||||
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor';
|
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor';
|
||||||
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
|
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
|
||||||
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
|
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
|
||||||
|
import { ApiService } from '@ghostfolio/api/services/api/api.service';
|
||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||||
import { parseDate } from '@ghostfolio/common/helper';
|
|
||||||
import {
|
import {
|
||||||
Filter,
|
|
||||||
PortfolioChart,
|
|
||||||
PortfolioDetails,
|
PortfolioDetails,
|
||||||
PortfolioInvestments,
|
PortfolioInvestments,
|
||||||
PortfolioPerformanceResponse,
|
PortfolioPerformanceResponse,
|
||||||
PortfolioPublicDetails,
|
PortfolioPublicDetails,
|
||||||
PortfolioReport,
|
PortfolioReport
|
||||||
PortfolioSummary
|
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
||||||
import type {
|
import type {
|
||||||
@ -40,6 +37,7 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import Big from 'big.js';
|
||||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
|
|
||||||
import { PortfolioPositionDetail } from './interfaces/portfolio-position-detail.interface';
|
import { PortfolioPositionDetail } from './interfaces/portfolio-position-detail.interface';
|
||||||
@ -52,6 +50,7 @@ export class PortfolioController {
|
|||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly accessService: AccessService,
|
private readonly accessService: AccessService,
|
||||||
|
private readonly apiService: ApiService,
|
||||||
private readonly configurationService: ConfigurationService,
|
private readonly configurationService: ConfigurationService,
|
||||||
private readonly exchangeRateDataService: ExchangeRateDataService,
|
private readonly exchangeRateDataService: ExchangeRateDataService,
|
||||||
private readonly portfolioService: PortfolioService,
|
private readonly portfolioService: PortfolioService,
|
||||||
@ -61,55 +60,6 @@ export class PortfolioController {
|
|||||||
this.baseCurrency = this.configurationService.get('BASE_CURRENCY');
|
this.baseCurrency = this.configurationService.get('BASE_CURRENCY');
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('chart')
|
|
||||||
@UseGuards(AuthGuard('jwt'))
|
|
||||||
public async getChart(
|
|
||||||
@Headers('impersonation-id') impersonationId: string,
|
|
||||||
@Query('range') range
|
|
||||||
): Promise<PortfolioChart> {
|
|
||||||
const historicalDataContainer = await this.portfolioService.getChart(
|
|
||||||
impersonationId,
|
|
||||||
range
|
|
||||||
);
|
|
||||||
|
|
||||||
let chartData = historicalDataContainer.items;
|
|
||||||
|
|
||||||
let hasError = false;
|
|
||||||
|
|
||||||
chartData.forEach((chartDataItem) => {
|
|
||||||
if (hasNotDefinedValuesInObject(chartDataItem)) {
|
|
||||||
hasError = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
impersonationId ||
|
|
||||||
this.userService.isRestrictedView(this.request.user)
|
|
||||||
) {
|
|
||||||
let maxValue = 0;
|
|
||||||
|
|
||||||
chartData.forEach((portfolioItem) => {
|
|
||||||
if (portfolioItem.value > maxValue) {
|
|
||||||
maxValue = portfolioItem.value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
chartData = chartData.map((historicalDataItem) => {
|
|
||||||
return {
|
|
||||||
...historicalDataItem,
|
|
||||||
marketPrice: Number((historicalDataItem.value / maxValue).toFixed(2))
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
hasError,
|
|
||||||
chart: chartData,
|
|
||||||
isAllTimeHigh: historicalDataContainer.isAllTimeHigh,
|
|
||||||
isAllTimeLow: historicalDataContainer.isAllTimeLow
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('details')
|
@Get('details')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
@UseInterceptors(RedactValuesInResponseInterceptor)
|
@UseInterceptors(RedactValuesInResponseInterceptor)
|
||||||
@ -118,37 +68,16 @@ export class PortfolioController {
|
|||||||
@Headers('impersonation-id') impersonationId: string,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
@Query('accounts') filterByAccounts?: string,
|
@Query('accounts') filterByAccounts?: string,
|
||||||
@Query('assetClasses') filterByAssetClasses?: string,
|
@Query('assetClasses') filterByAssetClasses?: string,
|
||||||
@Query('range') range?: DateRange,
|
@Query('range') dateRange: DateRange = 'max',
|
||||||
@Query('tags') filterByTags?: string
|
@Query('tags') filterByTags?: string
|
||||||
): Promise<PortfolioDetails & { hasError: boolean }> {
|
): Promise<PortfolioDetails & { hasError: boolean }> {
|
||||||
let hasError = false;
|
let hasError = false;
|
||||||
|
|
||||||
const accountIds = filterByAccounts?.split(',') ?? [];
|
const filters = this.apiService.buildFiltersFromQueryParams({
|
||||||
const assetClasses = filterByAssetClasses?.split(',') ?? [];
|
filterByAccounts,
|
||||||
const tagIds = filterByTags?.split(',') ?? [];
|
filterByAssetClasses,
|
||||||
|
filterByTags
|
||||||
const filters: Filter[] = [
|
});
|
||||||
...accountIds.map((accountId) => {
|
|
||||||
return <Filter>{
|
|
||||||
id: accountId,
|
|
||||||
type: 'ACCOUNT'
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
...assetClasses.map((assetClass) => {
|
|
||||||
return <Filter>{
|
|
||||||
id: assetClass,
|
|
||||||
type: 'ASSET_CLASS'
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
...tagIds.map((tagId) => {
|
|
||||||
return <Filter>{
|
|
||||||
id: tagId,
|
|
||||||
type: 'TAG'
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
let portfolioSummary: PortfolioSummary;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
accounts,
|
accounts,
|
||||||
@ -158,18 +87,18 @@ export class PortfolioController {
|
|||||||
holdings,
|
holdings,
|
||||||
summary,
|
summary,
|
||||||
totalValueInBaseCurrency
|
totalValueInBaseCurrency
|
||||||
} = await this.portfolioService.getDetails(
|
} = await this.portfolioService.getDetails({
|
||||||
|
dateRange,
|
||||||
|
filters,
|
||||||
impersonationId,
|
impersonationId,
|
||||||
this.request.user.id,
|
userId: this.request.user.id
|
||||||
range,
|
});
|
||||||
filters
|
|
||||||
);
|
|
||||||
|
|
||||||
if (hasErrors || hasNotDefinedValuesInObject(holdings)) {
|
if (hasErrors || hasNotDefinedValuesInObject(holdings)) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
portfolioSummary = summary;
|
let portfolioSummary = summary;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
impersonationId ||
|
impersonationId ||
|
||||||
@ -254,6 +183,7 @@ export class PortfolioController {
|
|||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async getInvestments(
|
public async getInvestments(
|
||||||
@Headers('impersonation-id') impersonationId: string,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
|
@Query('range') dateRange: DateRange = 'max',
|
||||||
@Query('groupBy') groupBy?: GroupBy
|
@Query('groupBy') groupBy?: GroupBy
|
||||||
): Promise<PortfolioInvestments> {
|
): Promise<PortfolioInvestments> {
|
||||||
if (
|
if (
|
||||||
@ -269,12 +199,16 @@ export class PortfolioController {
|
|||||||
let investments: InvestmentItem[];
|
let investments: InvestmentItem[];
|
||||||
|
|
||||||
if (groupBy === 'month') {
|
if (groupBy === 'month') {
|
||||||
investments = await this.portfolioService.getInvestments(
|
investments = await this.portfolioService.getInvestments({
|
||||||
|
dateRange,
|
||||||
impersonationId,
|
impersonationId,
|
||||||
'month'
|
groupBy: 'month'
|
||||||
);
|
});
|
||||||
} else {
|
} else {
|
||||||
investments = await this.portfolioService.getInvestments(impersonationId);
|
investments = await this.portfolioService.getInvestments({
|
||||||
|
dateRange,
|
||||||
|
impersonationId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -292,33 +226,7 @@ export class PortfolioController {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return { firstOrderDate: parseDate(investments[0]?.date), investments };
|
return { investments };
|
||||||
}
|
|
||||||
|
|
||||||
@Get('performance')
|
|
||||||
@UseGuards(AuthGuard('jwt'))
|
|
||||||
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
|
||||||
public async getPerformance(
|
|
||||||
@Headers('impersonation-id') impersonationId: string,
|
|
||||||
@Query('range') range
|
|
||||||
): Promise<PortfolioPerformanceResponse> {
|
|
||||||
const performanceInformation = await this.portfolioService.getPerformance(
|
|
||||||
impersonationId,
|
|
||||||
range
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
impersonationId ||
|
|
||||||
this.request.user.Settings.settings.viewMode === 'ZEN' ||
|
|
||||||
this.userService.isRestrictedView(this.request.user)
|
|
||||||
) {
|
|
||||||
performanceInformation.performance = nullifyValuesInObject(
|
|
||||||
performanceInformation.performance,
|
|
||||||
['currentGrossPerformance', 'currentValue']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return performanceInformation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('performance')
|
@Get('performance')
|
||||||
@ -327,23 +235,42 @@ export class PortfolioController {
|
|||||||
@Version('2')
|
@Version('2')
|
||||||
public async getPerformanceV2(
|
public async getPerformanceV2(
|
||||||
@Headers('impersonation-id') impersonationId: string,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
@Query('range') dateRange
|
@Query('range') dateRange: DateRange = 'max'
|
||||||
): Promise<PortfolioPerformanceResponse> {
|
): Promise<PortfolioPerformanceResponse> {
|
||||||
const performanceInformation = await this.portfolioService.getPerformanceV2(
|
const performanceInformation = await this.portfolioService.getPerformance({
|
||||||
{
|
dateRange,
|
||||||
dateRange,
|
impersonationId,
|
||||||
impersonationId
|
userId: this.request.user.id
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
impersonationId ||
|
impersonationId ||
|
||||||
this.request.user.Settings.settings.viewMode === 'ZEN' ||
|
this.request.user.Settings.settings.viewMode === 'ZEN' ||
|
||||||
this.userService.isRestrictedView(this.request.user)
|
this.userService.isRestrictedView(this.request.user)
|
||||||
) {
|
) {
|
||||||
|
performanceInformation.chart = performanceInformation.chart.map(
|
||||||
|
({ date, netPerformanceInPercentage, totalInvestment, value }) => {
|
||||||
|
return {
|
||||||
|
date,
|
||||||
|
netPerformanceInPercentage,
|
||||||
|
totalInvestment: new Big(totalInvestment)
|
||||||
|
.div(performanceInformation.performance.totalInvestment)
|
||||||
|
.toNumber(),
|
||||||
|
value: new Big(value)
|
||||||
|
.div(performanceInformation.performance.currentValue)
|
||||||
|
.toNumber()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
performanceInformation.performance = nullifyValuesInObject(
|
performanceInformation.performance = nullifyValuesInObject(
|
||||||
performanceInformation.performance,
|
performanceInformation.performance,
|
||||||
['currentGrossPerformance', 'currentNetPerformance', 'currentValue']
|
[
|
||||||
|
'currentGrossPerformance',
|
||||||
|
'currentNetPerformance',
|
||||||
|
'currentValue',
|
||||||
|
'totalInvestment'
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,11 +282,11 @@ export class PortfolioController {
|
|||||||
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
||||||
public async getPositions(
|
public async getPositions(
|
||||||
@Headers('impersonation-id') impersonationId: string,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
@Query('range') range
|
@Query('range') dateRange: DateRange = 'max'
|
||||||
): Promise<PortfolioPositions> {
|
): Promise<PortfolioPositions> {
|
||||||
const result = await this.portfolioService.getPositions(
|
const result = await this.portfolioService.getPositions(
|
||||||
impersonationId,
|
impersonationId,
|
||||||
range
|
dateRange
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -400,12 +327,12 @@ export class PortfolioController {
|
|||||||
hasDetails = user.subscription.type === 'Premium';
|
hasDetails = user.subscription.type === 'Premium';
|
||||||
}
|
}
|
||||||
|
|
||||||
const { holdings } = await this.portfolioService.getDetails(
|
const { holdings } = await this.portfolioService.getDetails({
|
||||||
access.userId,
|
dateRange: 'max',
|
||||||
access.userId,
|
filters: [{ id: 'EQUITY', type: 'ASSET_CLASS' }],
|
||||||
'max',
|
impersonationId: access.userId,
|
||||||
[{ id: 'EQUITY', type: 'ASSET_CLASS' }]
|
userId: user.id
|
||||||
);
|
});
|
||||||
|
|
||||||
const portfolioPublicDetails: PortfolioPublicDetails = {
|
const portfolioPublicDetails: PortfolioPublicDetails = {
|
||||||
hasDetails,
|
hasDetails,
|
||||||
|
@ -2,6 +2,7 @@ import { AccessModule } from '@ghostfolio/api/app/access/access.module';
|
|||||||
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
||||||
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
|
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
|
||||||
import { UserModule } from '@ghostfolio/api/app/user/user.module';
|
import { UserModule } from '@ghostfolio/api/app/user/user.module';
|
||||||
|
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
|
||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
||||||
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
|
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
|
||||||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||||
@ -22,6 +23,7 @@ import { RulesService } from './rules.service';
|
|||||||
exports: [PortfolioService],
|
exports: [PortfolioService],
|
||||||
imports: [
|
imports: [
|
||||||
AccessModule,
|
AccessModule,
|
||||||
|
ApiModule,
|
||||||
ConfigurationModule,
|
ConfigurationModule,
|
||||||
DataGatheringModule,
|
DataGatheringModule,
|
||||||
DataProviderModule,
|
DataProviderModule,
|
||||||
|
@ -3,7 +3,6 @@ import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details
|
|||||||
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface';
|
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface';
|
||||||
import { TimelineSpecification } from '@ghostfolio/api/app/portfolio/interfaces/timeline-specification.interface';
|
|
||||||
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
|
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
|
||||||
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||||
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
|
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
|
||||||
@ -36,7 +35,8 @@ import {
|
|||||||
PortfolioSummary,
|
PortfolioSummary,
|
||||||
Position,
|
Position,
|
||||||
TimelinePosition,
|
TimelinePosition,
|
||||||
UserSettings
|
UserSettings,
|
||||||
|
UserWithSettings
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
||||||
import type {
|
import type {
|
||||||
@ -67,11 +67,9 @@ import {
|
|||||||
isAfter,
|
isAfter,
|
||||||
isBefore,
|
isBefore,
|
||||||
max,
|
max,
|
||||||
parse,
|
|
||||||
parseISO,
|
parseISO,
|
||||||
set,
|
set,
|
||||||
setDayOfYear,
|
setDayOfYear,
|
||||||
startOfDay,
|
|
||||||
subDays,
|
subDays,
|
||||||
subYears
|
subYears
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
@ -107,15 +105,19 @@ export class PortfolioService {
|
|||||||
this.baseCurrency = this.configurationService.get('BASE_CURRENCY');
|
this.baseCurrency = this.configurationService.get('BASE_CURRENCY');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAccounts(
|
public async getAccounts({
|
||||||
aUserId: string,
|
filters,
|
||||||
aFilters?: Filter[],
|
userId,
|
||||||
withExcludedAccounts = false
|
withExcludedAccounts = false
|
||||||
): Promise<AccountWithValue[]> {
|
}: {
|
||||||
const where: Prisma.AccountWhereInput = { userId: aUserId };
|
filters?: Filter[];
|
||||||
|
userId: string;
|
||||||
|
withExcludedAccounts?: boolean;
|
||||||
|
}): Promise<AccountWithValue[]> {
|
||||||
|
const where: Prisma.AccountWhereInput = { userId: userId };
|
||||||
|
|
||||||
if (aFilters?.[0].id && aFilters?.[0].type === 'ACCOUNT') {
|
if (filters?.[0].id && filters?.[0].type === 'ACCOUNT') {
|
||||||
where.id = aFilters[0].id;
|
where.id = filters[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [accounts, details] = await Promise.all([
|
const [accounts, details] = await Promise.all([
|
||||||
@ -124,13 +126,12 @@ export class PortfolioService {
|
|||||||
include: { Order: true, Platform: true },
|
include: { Order: true, Platform: true },
|
||||||
orderBy: { name: 'asc' }
|
orderBy: { name: 'asc' }
|
||||||
}),
|
}),
|
||||||
this.getDetails(
|
this.getDetails({
|
||||||
aUserId,
|
filters,
|
||||||
aUserId,
|
withExcludedAccounts,
|
||||||
undefined,
|
impersonationId: userId,
|
||||||
aFilters,
|
userId: this.request.user.id
|
||||||
withExcludedAccounts
|
})
|
||||||
)
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
||||||
@ -168,16 +169,20 @@ export class PortfolioService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAccountsWithAggregations(
|
public async getAccountsWithAggregations({
|
||||||
aUserId: string,
|
filters,
|
||||||
aFilters?: Filter[],
|
userId,
|
||||||
withExcludedAccounts = false
|
withExcludedAccounts = false
|
||||||
): Promise<Accounts> {
|
}: {
|
||||||
const accounts = await this.getAccounts(
|
filters?: Filter[];
|
||||||
aUserId,
|
userId: string;
|
||||||
aFilters,
|
withExcludedAccounts?: boolean;
|
||||||
|
}): Promise<Accounts> {
|
||||||
|
const accounts = await this.getAccounts({
|
||||||
|
filters,
|
||||||
|
userId,
|
||||||
withExcludedAccounts
|
withExcludedAccounts
|
||||||
);
|
});
|
||||||
let totalBalanceInBaseCurrency = new Big(0);
|
let totalBalanceInBaseCurrency = new Big(0);
|
||||||
let totalValueInBaseCurrency = new Big(0);
|
let totalValueInBaseCurrency = new Big(0);
|
||||||
let transactionCount = 0;
|
let transactionCount = 0;
|
||||||
@ -200,11 +205,16 @@ export class PortfolioService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getInvestments(
|
public async getInvestments({
|
||||||
aImpersonationId: string,
|
dateRange,
|
||||||
groupBy?: GroupBy
|
impersonationId,
|
||||||
): Promise<InvestmentItem[]> {
|
groupBy
|
||||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
}: {
|
||||||
|
dateRange: DateRange;
|
||||||
|
impersonationId: string;
|
||||||
|
groupBy?: GroupBy;
|
||||||
|
}): Promise<InvestmentItem[]> {
|
||||||
|
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
||||||
|
|
||||||
const { portfolioOrders, transactionPoints } =
|
const { portfolioOrders, transactionPoints } =
|
||||||
await this.getTransactionPoints({
|
await this.getTransactionPoints({
|
||||||
@ -276,108 +286,32 @@ export class PortfolioService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortBy(investments, (investment) => {
|
investments = sortBy(investments, (investment) => {
|
||||||
return investment.date;
|
return investment.date;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
public async getChart(
|
const startDate = this.getStartDate(
|
||||||
aImpersonationId: string,
|
dateRange,
|
||||||
aDateRange: DateRange = 'max'
|
parseDate(investments[0]?.date)
|
||||||
): Promise<HistoricalDataContainer> {
|
);
|
||||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
|
||||||
|
|
||||||
const { portfolioOrders, transactionPoints } =
|
return investments.filter(({ date }) => {
|
||||||
await this.getTransactionPoints({
|
return !isBefore(parseDate(date), startDate);
|
||||||
userId
|
|
||||||
});
|
|
||||||
|
|
||||||
const portfolioCalculator = new PortfolioCalculator({
|
|
||||||
currency: this.request.user.Settings.settings.baseCurrency,
|
|
||||||
currentRateService: this.currentRateService,
|
|
||||||
orders: portfolioOrders
|
|
||||||
});
|
});
|
||||||
|
|
||||||
portfolioCalculator.setTransactionPoints(transactionPoints);
|
|
||||||
if (transactionPoints.length === 0) {
|
|
||||||
return {
|
|
||||||
isAllTimeHigh: false,
|
|
||||||
isAllTimeLow: false,
|
|
||||||
items: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
let portfolioStart = parse(
|
|
||||||
transactionPoints[0].date,
|
|
||||||
DATE_FORMAT,
|
|
||||||
new Date()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get start date for the full portfolio because of because of the
|
|
||||||
// min and max calculation
|
|
||||||
portfolioStart = this.getStartDate('max', portfolioStart);
|
|
||||||
|
|
||||||
const timelineSpecification: TimelineSpecification[] = [
|
|
||||||
{
|
|
||||||
start: format(portfolioStart, DATE_FORMAT),
|
|
||||||
accuracy: 'day'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const timelineInfo = await portfolioCalculator.calculateTimeline(
|
|
||||||
timelineSpecification,
|
|
||||||
format(new Date(), DATE_FORMAT)
|
|
||||||
);
|
|
||||||
|
|
||||||
const timeline = timelineInfo.timelinePeriods;
|
|
||||||
|
|
||||||
const items = timeline
|
|
||||||
.filter((timelineItem) => timelineItem !== null)
|
|
||||||
.map((timelineItem) => ({
|
|
||||||
date: timelineItem.date,
|
|
||||||
value: timelineItem.netPerformance.toNumber()
|
|
||||||
}));
|
|
||||||
|
|
||||||
let lastItem = null;
|
|
||||||
if (timeline.length > 0) {
|
|
||||||
lastItem = timeline[timeline.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
let isAllTimeHigh = timelineInfo.maxNetPerformance?.eq(
|
|
||||||
lastItem?.netPerformance ?? 0
|
|
||||||
);
|
|
||||||
let isAllTimeLow = timelineInfo.minNetPerformance?.eq(
|
|
||||||
lastItem?.netPerformance ?? 0
|
|
||||||
);
|
|
||||||
if (isAllTimeHigh && isAllTimeLow) {
|
|
||||||
isAllTimeHigh = false;
|
|
||||||
isAllTimeLow = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
portfolioStart = startOfDay(
|
|
||||||
this.getStartDate(
|
|
||||||
aDateRange,
|
|
||||||
parse(transactionPoints[0].date, DATE_FORMAT, new Date())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
isAllTimeHigh,
|
|
||||||
isAllTimeLow,
|
|
||||||
items: items.filter((item) => {
|
|
||||||
// Filter items of date range
|
|
||||||
return !isAfter(portfolioStart, parseDate(item.date));
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChartV2({
|
public async getChart({
|
||||||
dateRange = 'max',
|
dateRange = 'max',
|
||||||
impersonationId
|
impersonationId,
|
||||||
|
userCurrency,
|
||||||
|
userId
|
||||||
}: {
|
}: {
|
||||||
dateRange?: DateRange;
|
dateRange?: DateRange;
|
||||||
impersonationId: string;
|
impersonationId: string;
|
||||||
|
userCurrency: string;
|
||||||
|
userId: string;
|
||||||
}): Promise<HistoricalDataContainer> {
|
}): Promise<HistoricalDataContainer> {
|
||||||
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
userId = await this.getUserId(impersonationId, userId);
|
||||||
|
|
||||||
const { portfolioOrders, transactionPoints } =
|
const { portfolioOrders, transactionPoints } =
|
||||||
await this.getTransactionPoints({
|
await this.getTransactionPoints({
|
||||||
@ -385,7 +319,7 @@ export class PortfolioService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const portfolioCalculator = new PortfolioCalculator({
|
const portfolioCalculator = new PortfolioCalculator({
|
||||||
currency: this.request.user.Settings.settings.baseCurrency,
|
currency: userCurrency,
|
||||||
currentRateService: this.currentRateService,
|
currentRateService: this.currentRateService,
|
||||||
orders: portfolioOrders
|
orders: portfolioOrders
|
||||||
});
|
});
|
||||||
@ -421,29 +355,32 @@ export class PortfolioService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getDetails(
|
public async getDetails({
|
||||||
aImpersonationId: string,
|
impersonationId,
|
||||||
aUserId: string,
|
dateRange = 'max',
|
||||||
aDateRange: DateRange = 'max',
|
filters,
|
||||||
aFilters?: Filter[],
|
userId,
|
||||||
withExcludedAccounts = false
|
withExcludedAccounts = false
|
||||||
): Promise<PortfolioDetails & { hasErrors: boolean }> {
|
}: {
|
||||||
const userId = await this.getUserId(aImpersonationId, aUserId);
|
impersonationId: string;
|
||||||
|
dateRange?: DateRange;
|
||||||
|
filters?: Filter[];
|
||||||
|
userId: string;
|
||||||
|
withExcludedAccounts?: boolean;
|
||||||
|
}): Promise<PortfolioDetails & { hasErrors: boolean }> {
|
||||||
|
userId = await this.getUserId(impersonationId, userId);
|
||||||
const user = await this.userService.user({ id: userId });
|
const user = await this.userService.user({ id: userId });
|
||||||
|
const userCurrency = this.getUserCurrency(user);
|
||||||
|
|
||||||
const emergencyFund = new Big(
|
const emergencyFund = new Big(
|
||||||
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0
|
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0
|
||||||
);
|
);
|
||||||
const userCurrency =
|
|
||||||
user.Settings?.settings.baseCurrency ??
|
|
||||||
this.request.user?.Settings?.settings.baseCurrency ??
|
|
||||||
this.baseCurrency;
|
|
||||||
|
|
||||||
const { orders, portfolioOrders, transactionPoints } =
|
const { orders, portfolioOrders, transactionPoints } =
|
||||||
await this.getTransactionPoints({
|
await this.getTransactionPoints({
|
||||||
|
filters,
|
||||||
userId,
|
userId,
|
||||||
withExcludedAccounts,
|
withExcludedAccounts
|
||||||
filters: aFilters
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const portfolioCalculator = new PortfolioCalculator({
|
const portfolioCalculator = new PortfolioCalculator({
|
||||||
@ -457,15 +394,15 @@ export class PortfolioService {
|
|||||||
const portfolioStart = parseDate(
|
const portfolioStart = parseDate(
|
||||||
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT)
|
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT)
|
||||||
);
|
);
|
||||||
const startDate = this.getStartDate(aDateRange, portfolioStart);
|
const startDate = this.getStartDate(dateRange, portfolioStart);
|
||||||
const currentPositions = await portfolioCalculator.getCurrentPositions(
|
const currentPositions = await portfolioCalculator.getCurrentPositions(
|
||||||
startDate
|
startDate
|
||||||
);
|
);
|
||||||
|
|
||||||
const cashDetails = await this.accountService.getCashDetails({
|
const cashDetails = await this.accountService.getCashDetails({
|
||||||
|
filters,
|
||||||
userId,
|
userId,
|
||||||
currency: userCurrency,
|
currency: userCurrency
|
||||||
filters: aFilters
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const holdings: PortfolioDetails['holdings'] = {};
|
const holdings: PortfolioDetails['holdings'] = {};
|
||||||
@ -475,10 +412,10 @@ export class PortfolioService {
|
|||||||
let filteredValueInBaseCurrency = currentPositions.currentValue;
|
let filteredValueInBaseCurrency = currentPositions.currentValue;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
aFilters?.length === 0 ||
|
filters?.length === 0 ||
|
||||||
(aFilters?.length === 1 &&
|
(filters?.length === 1 &&
|
||||||
aFilters[0].type === 'ASSET_CLASS' &&
|
filters[0].type === 'ASSET_CLASS' &&
|
||||||
aFilters[0].id === 'CASH')
|
filters[0].id === 'CASH')
|
||||||
) {
|
) {
|
||||||
filteredValueInBaseCurrency = filteredValueInBaseCurrency.plus(
|
filteredValueInBaseCurrency = filteredValueInBaseCurrency.plus(
|
||||||
cashDetails.balanceInBaseCurrency
|
cashDetails.balanceInBaseCurrency
|
||||||
@ -574,10 +511,10 @@ export class PortfolioService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
aFilters?.length === 0 ||
|
filters?.length === 0 ||
|
||||||
(aFilters?.length === 1 &&
|
(filters?.length === 1 &&
|
||||||
aFilters[0].type === 'ASSET_CLASS' &&
|
filters[0].type === 'ASSET_CLASS' &&
|
||||||
aFilters[0].id === 'CASH')
|
filters[0].id === 'CASH')
|
||||||
) {
|
) {
|
||||||
const cashPositions = await this.getCashPositions({
|
const cashPositions = await this.getCashPositions({
|
||||||
cashDetails,
|
cashDetails,
|
||||||
@ -593,15 +530,19 @@ export class PortfolioService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const accounts = await this.getValueOfAccounts({
|
const accounts = await this.getValueOfAccounts({
|
||||||
|
filters,
|
||||||
orders,
|
orders,
|
||||||
portfolioItemsNow,
|
portfolioItemsNow,
|
||||||
userCurrency,
|
userCurrency,
|
||||||
userId,
|
userId,
|
||||||
withExcludedAccounts,
|
withExcludedAccounts
|
||||||
filters: aFilters
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const summary = await this.getSummary(aImpersonationId);
|
const summary = await this.getSummary({
|
||||||
|
impersonationId,
|
||||||
|
userCurrency,
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts,
|
accounts,
|
||||||
@ -621,8 +562,9 @@ export class PortfolioService {
|
|||||||
aImpersonationId: string,
|
aImpersonationId: string,
|
||||||
aSymbol: string
|
aSymbol: string
|
||||||
): Promise<PortfolioPositionDetail> {
|
): Promise<PortfolioPositionDetail> {
|
||||||
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
|
||||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
||||||
|
const user = await this.userService.user({ id: userId });
|
||||||
|
const userCurrency = this.getUserCurrency(user);
|
||||||
|
|
||||||
const orders = (
|
const orders = (
|
||||||
await this.orderService.getOrders({
|
await this.orderService.getOrders({
|
||||||
@ -942,85 +884,18 @@ export class PortfolioService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPerformance(
|
public async getPerformance({
|
||||||
aImpersonationId: string,
|
|
||||||
aDateRange: DateRange = 'max'
|
|
||||||
): Promise<PortfolioPerformanceResponse> {
|
|
||||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
|
||||||
|
|
||||||
const { portfolioOrders, transactionPoints } =
|
|
||||||
await this.getTransactionPoints({
|
|
||||||
userId
|
|
||||||
});
|
|
||||||
|
|
||||||
const portfolioCalculator = new PortfolioCalculator({
|
|
||||||
currency: this.request.user.Settings.settings.baseCurrency,
|
|
||||||
currentRateService: this.currentRateService,
|
|
||||||
orders: portfolioOrders
|
|
||||||
});
|
|
||||||
|
|
||||||
if (transactionPoints?.length <= 0) {
|
|
||||||
return {
|
|
||||||
hasErrors: false,
|
|
||||||
performance: {
|
|
||||||
currentGrossPerformance: 0,
|
|
||||||
currentGrossPerformancePercent: 0,
|
|
||||||
currentNetPerformance: 0,
|
|
||||||
currentNetPerformancePercent: 0,
|
|
||||||
currentValue: 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
portfolioCalculator.setTransactionPoints(transactionPoints);
|
|
||||||
|
|
||||||
const portfolioStart = parseDate(transactionPoints[0].date);
|
|
||||||
const startDate = this.getStartDate(aDateRange, portfolioStart);
|
|
||||||
const currentPositions = await portfolioCalculator.getCurrentPositions(
|
|
||||||
startDate
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasErrors = currentPositions.hasErrors;
|
|
||||||
const currentValue = currentPositions.currentValue.toNumber();
|
|
||||||
const currentGrossPerformance = currentPositions.grossPerformance;
|
|
||||||
let currentGrossPerformancePercent =
|
|
||||||
currentPositions.grossPerformancePercentage;
|
|
||||||
const currentNetPerformance = currentPositions.netPerformance;
|
|
||||||
let currentNetPerformancePercent =
|
|
||||||
currentPositions.netPerformancePercentage;
|
|
||||||
|
|
||||||
if (currentGrossPerformance.mul(currentGrossPerformancePercent).lt(0)) {
|
|
||||||
// If algebraic sign is different, harmonize it
|
|
||||||
currentGrossPerformancePercent = currentGrossPerformancePercent.mul(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentNetPerformance.mul(currentNetPerformancePercent).lt(0)) {
|
|
||||||
// If algebraic sign is different, harmonize it
|
|
||||||
currentNetPerformancePercent = currentNetPerformancePercent.mul(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
errors: currentPositions.errors,
|
|
||||||
hasErrors: currentPositions.hasErrors || hasErrors,
|
|
||||||
performance: {
|
|
||||||
currentValue,
|
|
||||||
currentGrossPerformance: currentGrossPerformance.toNumber(),
|
|
||||||
currentGrossPerformancePercent:
|
|
||||||
currentGrossPerformancePercent.toNumber(),
|
|
||||||
currentNetPerformance: currentNetPerformance.toNumber(),
|
|
||||||
currentNetPerformancePercent: currentNetPerformancePercent.toNumber()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getPerformanceV2({
|
|
||||||
dateRange = 'max',
|
dateRange = 'max',
|
||||||
impersonationId
|
impersonationId,
|
||||||
|
userId
|
||||||
}: {
|
}: {
|
||||||
dateRange?: DateRange;
|
dateRange?: DateRange;
|
||||||
impersonationId: string;
|
impersonationId: string;
|
||||||
|
userId: string;
|
||||||
}): Promise<PortfolioPerformanceResponse> {
|
}): Promise<PortfolioPerformanceResponse> {
|
||||||
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
userId = await this.getUserId(impersonationId, userId);
|
||||||
|
const user = await this.userService.user({ id: userId });
|
||||||
|
const userCurrency = this.getUserCurrency(user);
|
||||||
|
|
||||||
const { portfolioOrders, transactionPoints } =
|
const { portfolioOrders, transactionPoints } =
|
||||||
await this.getTransactionPoints({
|
await this.getTransactionPoints({
|
||||||
@ -1028,7 +903,7 @@ export class PortfolioService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const portfolioCalculator = new PortfolioCalculator({
|
const portfolioCalculator = new PortfolioCalculator({
|
||||||
currency: this.request.user.Settings.settings.baseCurrency,
|
currency: userCurrency,
|
||||||
currentRateService: this.currentRateService,
|
currentRateService: this.currentRateService,
|
||||||
orders: portfolioOrders
|
orders: portfolioOrders
|
||||||
});
|
});
|
||||||
@ -1036,13 +911,15 @@ export class PortfolioService {
|
|||||||
if (transactionPoints?.length <= 0) {
|
if (transactionPoints?.length <= 0) {
|
||||||
return {
|
return {
|
||||||
chart: [],
|
chart: [],
|
||||||
|
firstOrderDate: undefined,
|
||||||
hasErrors: false,
|
hasErrors: false,
|
||||||
performance: {
|
performance: {
|
||||||
currentGrossPerformance: 0,
|
currentGrossPerformance: 0,
|
||||||
currentGrossPerformancePercent: 0,
|
currentGrossPerformancePercent: 0,
|
||||||
currentNetPerformance: 0,
|
currentNetPerformance: 0,
|
||||||
currentNetPerformancePercent: 0,
|
currentNetPerformancePercent: 0,
|
||||||
currentValue: 0
|
currentValue: 0,
|
||||||
|
totalInvestment: 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1063,6 +940,7 @@ export class PortfolioService {
|
|||||||
let currentNetPerformance = currentPositions.netPerformance;
|
let currentNetPerformance = currentPositions.netPerformance;
|
||||||
let currentNetPerformancePercent =
|
let currentNetPerformancePercent =
|
||||||
currentPositions.netPerformancePercentage;
|
currentPositions.netPerformancePercentage;
|
||||||
|
const totalInvestment = currentPositions.totalInvestment;
|
||||||
|
|
||||||
// if (currentGrossPerformance.mul(currentGrossPerformancePercent).lt(0)) {
|
// if (currentGrossPerformance.mul(currentGrossPerformancePercent).lt(0)) {
|
||||||
// // If algebraic sign is different, harmonize it
|
// // If algebraic sign is different, harmonize it
|
||||||
@ -1074,9 +952,11 @@ export class PortfolioService {
|
|||||||
// currentNetPerformancePercent = currentNetPerformancePercent.mul(-1);
|
// currentNetPerformancePercent = currentNetPerformancePercent.mul(-1);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const historicalDataContainer = await this.getChartV2({
|
const historicalDataContainer = await this.getChart({
|
||||||
dateRange,
|
dateRange,
|
||||||
impersonationId
|
impersonationId,
|
||||||
|
userCurrency,
|
||||||
|
userId
|
||||||
});
|
});
|
||||||
|
|
||||||
const itemOfToday = historicalDataContainer.items.find((item) => {
|
const itemOfToday = historicalDataContainer.items.find((item) => {
|
||||||
@ -1092,14 +972,24 @@ export class PortfolioService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
chart: historicalDataContainer.items.map(
|
chart: historicalDataContainer.items.map(
|
||||||
({ date, netPerformanceInPercentage }) => {
|
({
|
||||||
|
date,
|
||||||
|
netPerformance,
|
||||||
|
netPerformanceInPercentage,
|
||||||
|
totalInvestment,
|
||||||
|
value
|
||||||
|
}) => {
|
||||||
return {
|
return {
|
||||||
date,
|
date,
|
||||||
value: netPerformanceInPercentage
|
netPerformance,
|
||||||
|
netPerformanceInPercentage,
|
||||||
|
totalInvestment,
|
||||||
|
value
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
errors: currentPositions.errors,
|
errors: currentPositions.errors,
|
||||||
|
firstOrderDate: parseDate(historicalDataContainer.items[0]?.date),
|
||||||
hasErrors: currentPositions.hasErrors || hasErrors,
|
hasErrors: currentPositions.hasErrors || hasErrors,
|
||||||
performance: {
|
performance: {
|
||||||
currentValue,
|
currentValue,
|
||||||
@ -1107,14 +997,16 @@ export class PortfolioService {
|
|||||||
currentGrossPerformancePercent:
|
currentGrossPerformancePercent:
|
||||||
currentGrossPerformancePercent.toNumber(),
|
currentGrossPerformancePercent.toNumber(),
|
||||||
currentNetPerformance: currentNetPerformance.toNumber(),
|
currentNetPerformance: currentNetPerformance.toNumber(),
|
||||||
currentNetPerformancePercent: currentNetPerformancePercent.toNumber()
|
currentNetPerformancePercent: currentNetPerformancePercent.toNumber(),
|
||||||
|
totalInvestment: totalInvestment.toNumber()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getReport(impersonationId: string): Promise<PortfolioReport> {
|
public async getReport(impersonationId: string): Promise<PortfolioReport> {
|
||||||
const currency = this.request.user.Settings.settings.baseCurrency;
|
|
||||||
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
||||||
|
const user = await this.userService.user({ id: userId });
|
||||||
|
const userCurrency = this.getUserCurrency(user);
|
||||||
|
|
||||||
const { orders, portfolioOrders, transactionPoints } =
|
const { orders, portfolioOrders, transactionPoints } =
|
||||||
await this.getTransactionPoints({
|
await this.getTransactionPoints({
|
||||||
@ -1128,7 +1020,7 @@ export class PortfolioService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const portfolioCalculator = new PortfolioCalculator({
|
const portfolioCalculator = new PortfolioCalculator({
|
||||||
currency,
|
currency: userCurrency,
|
||||||
currentRateService: this.currentRateService,
|
currentRateService: this.currentRateService,
|
||||||
orders: portfolioOrders
|
orders: portfolioOrders
|
||||||
});
|
});
|
||||||
@ -1148,7 +1040,7 @@ export class PortfolioService {
|
|||||||
orders,
|
orders,
|
||||||
portfolioItemsNow,
|
portfolioItemsNow,
|
||||||
userId,
|
userId,
|
||||||
userCurrency: currency
|
userCurrency
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
rules: {
|
rules: {
|
||||||
@ -1195,7 +1087,7 @@ export class PortfolioService {
|
|||||||
new FeeRatioInitialInvestment(
|
new FeeRatioInitialInvestment(
|
||||||
this.exchangeRateDataService,
|
this.exchangeRateDataService,
|
||||||
currentPositions.totalInvestment.toNumber(),
|
currentPositions.totalInvestment.toNumber(),
|
||||||
this.getFees(orders).toNumber()
|
this.getFees({ orders, userCurrency }).toNumber()
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
<UserSettings>this.request.user.Settings.settings
|
<UserSettings>this.request.user.Settings.settings
|
||||||
@ -1298,7 +1190,15 @@ export class PortfolioService {
|
|||||||
return cashPositions;
|
return cashPositions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDividend(orders: OrderWithAccount[], date = new Date(0)) {
|
private getDividend({
|
||||||
|
date = new Date(0),
|
||||||
|
orders,
|
||||||
|
userCurrency
|
||||||
|
}: {
|
||||||
|
date?: Date;
|
||||||
|
orders: OrderWithAccount[];
|
||||||
|
userCurrency: string;
|
||||||
|
}) {
|
||||||
return orders
|
return orders
|
||||||
.filter((order) => {
|
.filter((order) => {
|
||||||
// Filter out all orders before given date and type dividend
|
// Filter out all orders before given date and type dividend
|
||||||
@ -1311,7 +1211,7 @@ export class PortfolioService {
|
|||||||
return this.exchangeRateDataService.toCurrency(
|
return this.exchangeRateDataService.toCurrency(
|
||||||
new Big(order.quantity).mul(order.unitPrice).toNumber(),
|
new Big(order.quantity).mul(order.unitPrice).toNumber(),
|
||||||
order.SymbolProfile.currency,
|
order.SymbolProfile.currency,
|
||||||
this.request.user.Settings.settings.baseCurrency
|
userCurrency
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.reduce(
|
.reduce(
|
||||||
@ -1320,7 +1220,15 @@ export class PortfolioService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFees(orders: OrderWithAccount[], date = new Date(0)) {
|
private getFees({
|
||||||
|
date = new Date(0),
|
||||||
|
orders,
|
||||||
|
userCurrency
|
||||||
|
}: {
|
||||||
|
date?: Date;
|
||||||
|
orders: OrderWithAccount[];
|
||||||
|
userCurrency: string;
|
||||||
|
}) {
|
||||||
return orders
|
return orders
|
||||||
.filter((order) => {
|
.filter((order) => {
|
||||||
// Filter out all orders before given date
|
// Filter out all orders before given date
|
||||||
@ -1330,7 +1238,7 @@ export class PortfolioService {
|
|||||||
return this.exchangeRateDataService.toCurrency(
|
return this.exchangeRateDataService.toCurrency(
|
||||||
order.fee,
|
order.fee,
|
||||||
order.SymbolProfile.currency,
|
order.SymbolProfile.currency,
|
||||||
this.request.user.Settings.settings.baseCurrency
|
userCurrency
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.reduce(
|
.reduce(
|
||||||
@ -1379,14 +1287,22 @@ export class PortfolioService {
|
|||||||
return portfolioStart;
|
return portfolioStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getSummary(
|
private async getSummary({
|
||||||
aImpersonationId: string
|
impersonationId,
|
||||||
): Promise<PortfolioSummary> {
|
userCurrency,
|
||||||
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
userId
|
||||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
}: {
|
||||||
|
impersonationId: string;
|
||||||
|
userCurrency: string;
|
||||||
|
userId: string;
|
||||||
|
}): Promise<PortfolioSummary> {
|
||||||
|
userId = await this.getUserId(impersonationId, userId);
|
||||||
const user = await this.userService.user({ id: userId });
|
const user = await this.userService.user({ id: userId });
|
||||||
|
|
||||||
const performanceInformation = await this.getPerformance(aImpersonationId);
|
const performanceInformation = await this.getPerformance({
|
||||||
|
impersonationId,
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
|
||||||
const { balanceInBaseCurrency } = await this.accountService.getCashDetails({
|
const { balanceInBaseCurrency } = await this.accountService.getCashDetails({
|
||||||
userId,
|
userId,
|
||||||
@ -1407,11 +1323,11 @@ export class PortfolioService {
|
|||||||
return account?.isExcluded ?? false;
|
return account?.isExcluded ?? false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const dividend = this.getDividend(orders).toNumber();
|
const dividend = this.getDividend({ orders, userCurrency }).toNumber();
|
||||||
const emergencyFund = new Big(
|
const emergencyFund = new Big(
|
||||||
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0
|
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0
|
||||||
);
|
);
|
||||||
const fees = this.getFees(orders).toNumber();
|
const fees = this.getFees({ orders, userCurrency }).toNumber();
|
||||||
const firstOrderDate = orders[0]?.date;
|
const firstOrderDate = orders[0]?.date;
|
||||||
const items = this.getItems(orders).toNumber();
|
const items = this.getItems(orders).toNumber();
|
||||||
|
|
||||||
@ -1679,4 +1595,12 @@ export class PortfolioService {
|
|||||||
})
|
})
|
||||||
.reduce((previous, current) => previous + current, 0);
|
.reduce((previous, current) => previous + current, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getUserCurrency(aUser: UserWithSettings) {
|
||||||
|
return (
|
||||||
|
aUser.Settings?.settings.baseCurrency ??
|
||||||
|
this.request.user?.Settings?.settings.baseCurrency ??
|
||||||
|
this.baseCurrency
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
import type { DateRange, ViewMode } from '@ghostfolio/common/types';
|
import type {
|
||||||
|
ColorScheme,
|
||||||
|
DateRange,
|
||||||
|
ViewMode
|
||||||
|
} from '@ghostfolio/common/types';
|
||||||
import {
|
import {
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsIn,
|
IsIn,
|
||||||
@ -16,6 +20,10 @@ export class UpdateUserSettingDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
benchmark?: string;
|
benchmark?: string;
|
||||||
|
|
||||||
|
@IsIn(<ColorScheme[]>['DARK', 'LIGHT'])
|
||||||
|
@IsOptional()
|
||||||
|
colorScheme?: ColorScheme;
|
||||||
|
|
||||||
@IsIn(<DateRange[]>['1d', '1y', '5y', 'max', 'ytd'])
|
@IsIn(<DateRange[]>['1d', '1y', '5y', 'max', 'ytd'])
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
dateRange?: DateRange;
|
dateRange?: DateRange;
|
||||||
|
9
apps/api/src/services/api/api.module.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ApiService } from './api.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
exports: [ApiService],
|
||||||
|
providers: [ApiService]
|
||||||
|
})
|
||||||
|
export class ApiModule {}
|
42
apps/api/src/services/api/api.service.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { Filter } from '@ghostfolio/common/interfaces';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ApiService {
|
||||||
|
public constructor() {}
|
||||||
|
|
||||||
|
public buildFiltersFromQueryParams({
|
||||||
|
filterByAccounts,
|
||||||
|
filterByAssetClasses,
|
||||||
|
filterByTags
|
||||||
|
}: {
|
||||||
|
filterByAccounts?: string;
|
||||||
|
filterByAssetClasses?: string;
|
||||||
|
filterByTags?: string;
|
||||||
|
}): Filter[] {
|
||||||
|
const accountIds = filterByAccounts?.split(',') ?? [];
|
||||||
|
const assetClasses = filterByAssetClasses?.split(',') ?? [];
|
||||||
|
const tagIds = filterByTags?.split(',') ?? [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
...accountIds.map((accountId) => {
|
||||||
|
return <Filter>{
|
||||||
|
id: accountId,
|
||||||
|
type: 'ACCOUNT'
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
...assetClasses.map((assetClass) => {
|
||||||
|
return <Filter>{
|
||||||
|
id: assetClass,
|
||||||
|
type: 'ASSET_CLASS'
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
...tagIds.map((tagId) => {
|
||||||
|
return <Filter>{
|
||||||
|
id: tagId,
|
||||||
|
type: 'TAG'
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,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 }),
|
||||||
RAKUTEN_RAPID_API_KEY: str({ default: '' }),
|
RAPID_API_API_KEY: str({ default: '' }),
|
||||||
REDIS_HOST: host({ default: 'localhost' }),
|
REDIS_HOST: host({ default: 'localhost' }),
|
||||||
REDIS_PASSWORD: str({ default: '' }),
|
REDIS_PASSWORD: str({ default: '' }),
|
||||||
REDIS_PORT: port({ default: 6379 }),
|
REDIS_PORT: port({ default: 6379 }),
|
||||||
|
@ -280,7 +280,7 @@ export class DataGatheringService {
|
|||||||
return (
|
return (
|
||||||
dataSource !== DataSource.GHOSTFOLIO &&
|
dataSource !== DataSource.GHOSTFOLIO &&
|
||||||
dataSource !== DataSource.MANUAL &&
|
dataSource !== DataSource.MANUAL &&
|
||||||
dataSource !== DataSource.RAKUTEN
|
dataSource !== DataSource.RAPID_API
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.map(({ dataSource, symbol }) => {
|
.map(({ dataSource, symbol }) => {
|
||||||
|
@ -5,7 +5,7 @@ import { EodHistoricalDataService } from '@ghostfolio/api/services/data-provider
|
|||||||
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||||
import { GoogleSheetsService } from '@ghostfolio/api/services/data-provider/google-sheets/google-sheets.service';
|
import { GoogleSheetsService } from '@ghostfolio/api/services/data-provider/google-sheets/google-sheets.service';
|
||||||
import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service';
|
import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service';
|
||||||
import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
import { RapidApiService } from '@ghostfolio/api/services/data-provider/rapid-api/rapid-api.service';
|
||||||
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
|
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||||
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
|
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
|
||||||
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module';
|
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module';
|
||||||
@ -27,7 +27,7 @@ import { DataProviderService } from './data-provider.service';
|
|||||||
GhostfolioScraperApiService,
|
GhostfolioScraperApiService,
|
||||||
GoogleSheetsService,
|
GoogleSheetsService,
|
||||||
ManualService,
|
ManualService,
|
||||||
RakutenRapidApiService,
|
RapidApiService,
|
||||||
YahooFinanceService,
|
YahooFinanceService,
|
||||||
{
|
{
|
||||||
inject: [
|
inject: [
|
||||||
@ -36,7 +36,7 @@ import { DataProviderService } from './data-provider.service';
|
|||||||
GhostfolioScraperApiService,
|
GhostfolioScraperApiService,
|
||||||
GoogleSheetsService,
|
GoogleSheetsService,
|
||||||
ManualService,
|
ManualService,
|
||||||
RakutenRapidApiService,
|
RapidApiService,
|
||||||
YahooFinanceService
|
YahooFinanceService
|
||||||
],
|
],
|
||||||
provide: 'DataProviderInterfaces',
|
provide: 'DataProviderInterfaces',
|
||||||
@ -46,7 +46,7 @@ import { DataProviderService } from './data-provider.service';
|
|||||||
ghostfolioScraperApiService,
|
ghostfolioScraperApiService,
|
||||||
googleSheetsService,
|
googleSheetsService,
|
||||||
manualService,
|
manualService,
|
||||||
rakutenRapidApiService,
|
rapidApiService,
|
||||||
yahooFinanceService
|
yahooFinanceService
|
||||||
) => [
|
) => [
|
||||||
alphaVantageService,
|
alphaVantageService,
|
||||||
@ -54,7 +54,7 @@ import { DataProviderService } from './data-provider.service';
|
|||||||
ghostfolioScraperApiService,
|
ghostfolioScraperApiService,
|
||||||
googleSheetsService,
|
googleSheetsService,
|
||||||
manualService,
|
manualService,
|
||||||
rakutenRapidApiService,
|
rapidApiService,
|
||||||
yahooFinanceService
|
yahooFinanceService
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export interface IRakutenRapidApiResponse {}
|
|
@ -0,0 +1 @@
|
|||||||
|
export interface IRapidApiResponse {}
|
@ -15,14 +15,14 @@ import bent from 'bent';
|
|||||||
import { format, subMonths, subWeeks, subYears } from 'date-fns';
|
import { format, subMonths, subWeeks, subYears } from 'date-fns';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RakutenRapidApiService implements DataProviderInterface {
|
export class RapidApiService implements DataProviderInterface {
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly configurationService: ConfigurationService,
|
private readonly configurationService: ConfigurationService,
|
||||||
private readonly prismaService: PrismaService
|
private readonly prismaService: PrismaService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public canHandle(symbol: string) {
|
public canHandle(symbol: string) {
|
||||||
return !!this.configurationService.get('RAKUTEN_RAPID_API_KEY');
|
return !!this.configurationService.get('RAPID_API_API_KEY');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetProfile(
|
public async getAssetProfile(
|
||||||
@ -103,7 +103,7 @@ export class RakutenRapidApiService implements DataProviderInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getName(): DataSource {
|
public getName(): DataSource {
|
||||||
return DataSource.RAKUTEN;
|
return DataSource.RAPID_API;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getQuotes(
|
public async getQuotes(
|
||||||
@ -129,7 +129,7 @@ export class RakutenRapidApiService implements DataProviderInterface {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(error, 'RakutenRapidApiService');
|
Logger.error(error, 'RapidApiService');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
@ -155,16 +155,14 @@ export class RakutenRapidApiService implements DataProviderInterface {
|
|||||||
{
|
{
|
||||||
useQueryString: true,
|
useQueryString: true,
|
||||||
'x-rapidapi-host': 'fear-and-greed-index.p.rapidapi.com',
|
'x-rapidapi-host': 'fear-and-greed-index.p.rapidapi.com',
|
||||||
'x-rapidapi-key': this.configurationService.get(
|
'x-rapidapi-key': this.configurationService.get('RAPID_API_API_KEY')
|
||||||
'RAKUTEN_RAPID_API_KEY'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const { fgi } = await get();
|
const { fgi } = await get();
|
||||||
return fgi;
|
return fgi;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(error, 'RakutenRapidApiService');
|
Logger.error(error, 'RapidApiService');
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
@ -58,8 +58,15 @@ export class YahooFinanceService implements DataProviderInterface {
|
|||||||
* DOGEUSD -> DOGE-USD
|
* DOGEUSD -> DOGE-USD
|
||||||
*/
|
*/
|
||||||
public convertToYahooFinanceSymbol(aSymbol: string) {
|
public convertToYahooFinanceSymbol(aSymbol: string) {
|
||||||
if (aSymbol.includes(this.baseCurrency) && aSymbol.length >= 6) {
|
if (
|
||||||
if (isCurrency(aSymbol.substring(0, aSymbol.length - 3))) {
|
aSymbol.includes(this.baseCurrency) &&
|
||||||
|
aSymbol.length > this.baseCurrency.length
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
isCurrency(
|
||||||
|
aSymbol.substring(0, aSymbol.length - this.baseCurrency.length)
|
||||||
|
)
|
||||||
|
) {
|
||||||
return `${aSymbol}=X`;
|
return `${aSymbol}=X`;
|
||||||
} else if (
|
} else if (
|
||||||
this.cryptocurrencyService.isCryptocurrency(
|
this.cryptocurrencyService.isCryptocurrency(
|
||||||
|
@ -26,7 +26,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;
|
||||||
RAKUTEN_RAPID_API_KEY: string;
|
RAPID_API_API_KEY: string;
|
||||||
REDIS_HOST: string;
|
REDIS_HOST: string;
|
||||||
REDIS_PASSWORD: string;
|
REDIS_PASSWORD: string;
|
||||||
REDIS_PORT: number;
|
REDIS_PORT: number;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { IsString } from 'class-validator';
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class PropertyDto {
|
export class PropertyDto {
|
||||||
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
@ -53,13 +53,15 @@ export class TwitterBotService {
|
|||||||
symbolItem.marketPrice
|
symbolItem.marketPrice
|
||||||
);
|
);
|
||||||
|
|
||||||
let status = `Current Market Mood: ${emoji} ${text} (${symbolItem.marketPrice}/100)`;
|
let status = `Current market mood is ${emoji} ${text.toLowerCase()} (${
|
||||||
|
symbolItem.marketPrice
|
||||||
|
}/100)`;
|
||||||
|
|
||||||
const benchmarkListing = await this.getBenchmarkListing(3);
|
const benchmarkListing = await this.getBenchmarkListing(3);
|
||||||
|
|
||||||
if (benchmarkListing?.length > 1) {
|
if (benchmarkListing?.length > 1) {
|
||||||
status += '\n\n';
|
status += '\n\n';
|
||||||
status += '±% from ATH\n';
|
status += '± from ATH in %\n';
|
||||||
status += benchmarkListing;
|
status += benchmarkListing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
22
apps/client-e2e/project.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "apps/client-e2e/src",
|
||||||
|
"projectType": "application",
|
||||||
|
"targets": {
|
||||||
|
"e2e": {
|
||||||
|
"executor": "@nrwl/cypress:cypress",
|
||||||
|
"options": {
|
||||||
|
"cypressConfig": "apps/client-e2e/cypress.json",
|
||||||
|
"tsConfig": "apps/client-e2e/tsconfig.e2e.json",
|
||||||
|
"devServerTarget": "client:serve"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"devServerTarget": "client:serve:production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [],
|
||||||
|
"implicitDependencies": ["client"]
|
||||||
|
}
|
201
apps/client/project.json
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"projectType": "application",
|
||||||
|
"generators": {
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"style": "scss"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceRoot": "apps/client/src",
|
||||||
|
"prefix": "gf",
|
||||||
|
"targets": {
|
||||||
|
"build": {
|
||||||
|
"executor": "@angular-devkit/build-angular:browser",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/apps/client",
|
||||||
|
"index": "apps/client/src/index.html",
|
||||||
|
"main": "apps/client/src/main.ts",
|
||||||
|
"polyfills": "apps/client/src/polyfills.ts",
|
||||||
|
"tsConfig": "apps/client/tsconfig.app.json",
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"glob": "assetlinks.json",
|
||||||
|
"input": "apps/client/src/assets",
|
||||||
|
"output": "./../.well-known"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"glob": "CHANGELOG.md",
|
||||||
|
"input": "",
|
||||||
|
"output": "./../assets"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"glob": "LICENSE",
|
||||||
|
"input": "",
|
||||||
|
"output": "./../assets"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"glob": "robots.txt",
|
||||||
|
"input": "apps/client/src/assets",
|
||||||
|
"output": "./../"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"glob": "sitemap.xml",
|
||||||
|
"input": "apps/client/src/assets",
|
||||||
|
"output": "./../"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "node_modules/ionicons/dist/ionicons",
|
||||||
|
"output": "./../ionicons"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"glob": "**/*.js",
|
||||||
|
"input": "node_modules/ionicons/dist/",
|
||||||
|
"output": "./../"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"glob": "**/*",
|
||||||
|
"input": "apps/client/src/assets",
|
||||||
|
"output": "./../assets/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"styles": ["apps/client/src/styles.scss"],
|
||||||
|
"scripts": ["node_modules/marked/marked.min.js"],
|
||||||
|
"vendorChunk": true,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"buildOptimizer": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"optimization": false,
|
||||||
|
"namedChunks": true
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"development-de": {
|
||||||
|
"baseHref": "/de/",
|
||||||
|
"localize": ["de"]
|
||||||
|
},
|
||||||
|
"development-en": {
|
||||||
|
"baseHref": "/en/",
|
||||||
|
"localize": ["en"]
|
||||||
|
},
|
||||||
|
"development-es": {
|
||||||
|
"baseHref": "/es/",
|
||||||
|
"localize": ["es"]
|
||||||
|
},
|
||||||
|
"development-it": {
|
||||||
|
"baseHref": "/it/",
|
||||||
|
"localize": ["it"]
|
||||||
|
},
|
||||||
|
"development-nl": {
|
||||||
|
"baseHref": "/nl/",
|
||||||
|
"localize": ["nl"]
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "apps/client/src/environments/environment.ts",
|
||||||
|
"with": "apps/client/src/environments/environment.prod.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"optimization": true,
|
||||||
|
"outputHashing": "all",
|
||||||
|
"sourceMap": false,
|
||||||
|
"namedChunks": false,
|
||||||
|
"extractLicenses": true,
|
||||||
|
"vendorChunk": false,
|
||||||
|
"buildOptimizer": true,
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "2mb",
|
||||||
|
"maximumError": "5mb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "6kb",
|
||||||
|
"maximumError": "10kb"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": ["{options.outputPath}"],
|
||||||
|
"defaultConfiguration": ""
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"executor": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "client:build",
|
||||||
|
"proxyConfig": "apps/client/proxy.conf.json"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"development-de": {
|
||||||
|
"browserTarget": "client:build:development-de"
|
||||||
|
},
|
||||||
|
"development-en": {
|
||||||
|
"browserTarget": "client:build:development-en"
|
||||||
|
},
|
||||||
|
"development-es": {
|
||||||
|
"browserTarget": "client:build:development-es"
|
||||||
|
},
|
||||||
|
"development-it": {
|
||||||
|
"browserTarget": "client:build:development-it"
|
||||||
|
},
|
||||||
|
"development-nl": {
|
||||||
|
"browserTarget": "client:build:development-nl"
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"browserTarget": "client:build:production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"executor": "ng-extract-i18n-merge:ng-extract-i18n-merge",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "client:build",
|
||||||
|
"includeContext": true,
|
||||||
|
"outputPath": "src/locales",
|
||||||
|
"targetFiles": [
|
||||||
|
"messages.de.xlf",
|
||||||
|
"messages.es.xlf",
|
||||||
|
"messages.it.xlf",
|
||||||
|
"messages.nl.xlf"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nrwl/linter:eslint",
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": ["apps/client/**/*.ts"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"executor": "@nrwl/jest:jest",
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "apps/client/jest.config.ts",
|
||||||
|
"passWithNoTests": true
|
||||||
|
},
|
||||||
|
"outputs": ["{workspaceRoot}/coverage/apps/client"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"i18n": {
|
||||||
|
"locales": {
|
||||||
|
"de": {
|
||||||
|
"baseHref": "/de/",
|
||||||
|
"translation": "apps/client/src/locales/messages.de.xlf"
|
||||||
|
},
|
||||||
|
"es": {
|
||||||
|
"baseHref": "/es/",
|
||||||
|
"translation": "apps/client/src/locales/messages.es.xlf"
|
||||||
|
},
|
||||||
|
"it": {
|
||||||
|
"baseHref": "/it/",
|
||||||
|
"translation": "apps/client/src/locales/messages.it.xlf"
|
||||||
|
},
|
||||||
|
"nl": {
|
||||||
|
"baseHref": "/nl/",
|
||||||
|
"translation": "apps/client/src/locales/messages.nl.xlf"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceLocale": "en"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -95,6 +95,13 @@ const routes: Routes = [
|
|||||||
'./pages/blog/2022/08/500-stars-on-github/500-stars-on-github-page.module'
|
'./pages/blog/2022/08/500-stars-on-github/500-stars-on-github-page.module'
|
||||||
).then((m) => m.FiveHundredStarsOnGitHubPageModule)
|
).then((m) => m.FiveHundredStarsOnGitHubPageModule)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'blog/2022/10/hacktoberfest-2022',
|
||||||
|
loadChildren: () =>
|
||||||
|
import(
|
||||||
|
'./pages/blog/2022/10/hacktoberfest-2022/hacktoberfest-2022-page.module'
|
||||||
|
).then((m) => m.Hacktoberfest2022PageModule)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'demo',
|
path: 'demo',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
} from '@ghostfolio/common/config';
|
} from '@ghostfolio/common/config';
|
||||||
import { InfoItem, User } from '@ghostfolio/common/interfaces';
|
import { InfoItem, User } from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
|
import { ColorScheme } from '@ghostfolio/common/types';
|
||||||
import { MaterialCssVarsService } from 'angular-material-css-vars';
|
import { MaterialCssVarsService } from 'angular-material-css-vars';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
@ -77,6 +78,8 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
permissions.createUserAccount
|
permissions.createUserAccount
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.initializeTheme(this.user?.settings.colorScheme);
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -97,13 +100,17 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
this.unsubscribeSubject.complete();
|
this.unsubscribeSubject.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeTheme() {
|
private initializeTheme(userPreferredColorScheme?: ColorScheme) {
|
||||||
this.materialCssVarsService.setDarkTheme(
|
const isDarkTheme = userPreferredColorScheme
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
? userPreferredColorScheme === 'DARK'
|
||||||
);
|
: window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
|
||||||
|
this.materialCssVarsService.setDarkTheme(isDarkTheme);
|
||||||
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addListener((event) => {
|
window.matchMedia('(prefers-color-scheme: dark)').addListener((event) => {
|
||||||
this.materialCssVarsService.setDarkTheme(event.matches);
|
if (!this.user?.settings.colorScheme) {
|
||||||
|
this.materialCssVarsService.setDarkTheme(event.matches);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.materialCssVarsService.setPrimaryColor(primaryColorHex);
|
this.materialCssVarsService.setPrimaryColor(primaryColorHex);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<gf-line-chart
|
<gf-line-chart
|
||||||
class="mb-4"
|
class="mb-4"
|
||||||
[historicalDataItems]="historicalDataItems"
|
[historicalDataItems]="historicalDataItems"
|
||||||
|
[isAnimated]="true"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[showXAxis]="true"
|
[showXAxis]="true"
|
||||||
[showYAxis]="true"
|
[showYAxis]="true"
|
||||||
|
@ -99,7 +99,7 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
|
|||||||
...this.coupons,
|
...this.coupons,
|
||||||
{ code: this.generateCouponCode(16), duration: this.couponDuration }
|
{ code: this.generateCouponCode(16), duration: this.couponDuration }
|
||||||
];
|
];
|
||||||
this.putCoupons(coupons);
|
this.putAdminSetting({ key: PROPERTY_COUPONS, value: coupons });
|
||||||
}
|
}
|
||||||
|
|
||||||
public onAddCurrency() {
|
public onAddCurrency() {
|
||||||
@ -107,7 +107,7 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
if (currency) {
|
if (currency) {
|
||||||
const currencies = uniq([...this.customCurrencies, currency]);
|
const currencies = uniq([...this.customCurrencies, currency]);
|
||||||
this.putCurrencies(currencies);
|
this.putAdminSetting({ key: PROPERTY_CURRENCIES, value: currencies });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
|
|||||||
const coupons = this.coupons.filter((coupon) => {
|
const coupons = this.coupons.filter((coupon) => {
|
||||||
return coupon.code !== aCouponCode;
|
return coupon.code !== aCouponCode;
|
||||||
});
|
});
|
||||||
this.putCoupons(coupons);
|
this.putAdminSetting({ key: PROPERTY_COUPONS, value: coupons });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,12 +137,12 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
|
|||||||
const currencies = this.customCurrencies.filter((currency) => {
|
const currencies = this.customCurrencies.filter((currency) => {
|
||||||
return currency !== aCurrency;
|
return currency !== aCurrency;
|
||||||
});
|
});
|
||||||
this.putCurrencies(currencies);
|
this.putAdminSetting({ key: PROPERTY_CURRENCIES, value: currencies });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onDeleteSystemMessage() {
|
public onDeleteSystemMessage() {
|
||||||
this.putSystemMessage('');
|
this.putAdminSetting({ key: PROPERTY_SYSTEM_MESSAGE, value: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
public onFlushCache() {
|
public onFlushCache() {
|
||||||
@ -192,14 +192,20 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onReadOnlyModeChange(aEvent: MatSlideToggleChange) {
|
public onReadOnlyModeChange(aEvent: MatSlideToggleChange) {
|
||||||
this.setReadOnlyMode(aEvent.checked);
|
this.putAdminSetting({
|
||||||
|
key: PROPERTY_IS_READ_ONLY_MODE,
|
||||||
|
value: aEvent.checked ? true : undefined
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSetSystemMessage() {
|
public onSetSystemMessage() {
|
||||||
const systemMessage = prompt($localize`Please set your system message:`);
|
const systemMessage = prompt($localize`Please set your system message:`);
|
||||||
|
|
||||||
if (systemMessage) {
|
if (systemMessage) {
|
||||||
this.putSystemMessage(systemMessage);
|
this.putAdminSetting({
|
||||||
|
key: PROPERTY_SYSTEM_MESSAGE,
|
||||||
|
value: systemMessage
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,49 +242,10 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
|
|||||||
return couponCode;
|
return couponCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private putCoupons(aCoupons: Coupon[]) {
|
private putAdminSetting({ key, value }: { key: string; value: any }) {
|
||||||
this.dataService
|
this.dataService
|
||||||
.putAdminSetting(PROPERTY_COUPONS, {
|
.putAdminSetting(key, {
|
||||||
value: JSON.stringify(aCoupons)
|
value: value ? JSON.stringify(value) : undefined
|
||||||
})
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 300);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private putCurrencies(aCurrencies: string[]) {
|
|
||||||
this.dataService
|
|
||||||
.putAdminSetting(PROPERTY_CURRENCIES, {
|
|
||||||
value: JSON.stringify(aCurrencies)
|
|
||||||
})
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 300);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private putSystemMessage(aSystemMessage: string) {
|
|
||||||
this.dataService
|
|
||||||
.putAdminSetting(PROPERTY_SYSTEM_MESSAGE, {
|
|
||||||
value: aSystemMessage
|
|
||||||
})
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 300);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private setReadOnlyMode(aValue: boolean) {
|
|
||||||
this.dataService
|
|
||||||
.putAdminSetting(PROPERTY_IS_READ_ONLY_MODE, {
|
|
||||||
value: aValue ? 'true' : ''
|
|
||||||
})
|
})
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
|
@ -5,15 +5,26 @@
|
|||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<div class="d-flex my-3">
|
<div class="d-flex my-3">
|
||||||
<div class="w-50" i18n>User Count</div>
|
<div class="w-50" i18n>User Count</div>
|
||||||
<div class="w-50">{{ userCount }}</div>
|
<div class="w-50">
|
||||||
|
<gf-value
|
||||||
|
precision="0"
|
||||||
|
[locale]="user?.settings?.locale"
|
||||||
|
[value]="userCount"
|
||||||
|
></gf-value>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex my-3">
|
<div class="d-flex my-3">
|
||||||
<div class="w-50" i18n>Activity Count</div>
|
<div class="w-50" i18n>Activity Count</div>
|
||||||
<div class="w-50">
|
<div class="w-50">
|
||||||
<ng-container *ngIf="transactionCount">
|
<gf-value
|
||||||
{{ transactionCount }} ({{ transactionCount / userCount | number
|
precision="0"
|
||||||
: '1.2-2' }} <span i18n>per User</span>)
|
[locale]="user?.settings?.locale"
|
||||||
</ng-container>
|
[value]="transactionCount"
|
||||||
|
></gf-value>
|
||||||
|
<div *ngIf="transactionCount && userCount">
|
||||||
|
{{ transactionCount / userCount | number : '1.2-2' }}
|
||||||
|
<span i18n>per User</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex my-3">
|
<div class="d-flex my-3">
|
||||||
@ -108,10 +119,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="align-items-start d-flex my-3">
|
||||||
|
<div class="w-50" i18n>Benchmarks</div>
|
||||||
|
<div class="w-50">
|
||||||
|
<table>
|
||||||
|
<tr *ngFor="let benchmark of info?.benchmarks">
|
||||||
|
<td class="pl-1">{{ benchmark.symbol }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div *ngIf="hasPermissionForSystemMessage" class="d-flex my-3">
|
<div *ngIf="hasPermissionForSystemMessage" class="d-flex my-3">
|
||||||
<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="info.systemMessage">
|
<div *ngIf="info?.systemMessage">
|
||||||
<span>{{ info.systemMessage }}</span>
|
<span>{{ info.systemMessage }}</span>
|
||||||
<button
|
<button
|
||||||
class="mini-icon mx-1 no-min-width px-2"
|
class="mini-icon mx-1 no-min-width px-2"
|
||||||
@ -122,7 +143,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
*ngIf="!info.systemMessage"
|
*ngIf="!info?.systemMessage"
|
||||||
color="accent"
|
color="accent"
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
(click)="onSetSystemMessage()"
|
(click)="onSetSystemMessage()"
|
||||||
|
@ -43,23 +43,23 @@
|
|||||||
<td class="mat-cell px-1 py-2 text-right">
|
<td class="mat-cell px-1 py-2 text-right">
|
||||||
{{ formatDistanceToNow(userItem.createdAt) }}
|
{{ formatDistanceToNow(userItem.createdAt) }}
|
||||||
</td>
|
</td>
|
||||||
<td class="mat-cell px-1 py-2">
|
<td class="mat-cell px-1 py-2 text-right">
|
||||||
<gf-value
|
<gf-value
|
||||||
class="align-items-end"
|
class="d-inline-block justify-content-end"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[value]="userItem.accountCount"
|
[value]="userItem.accountCount"
|
||||||
></gf-value>
|
></gf-value>
|
||||||
</td>
|
</td>
|
||||||
<td class="mat-cell px-1 py-2">
|
<td class="mat-cell px-1 py-2 text-right">
|
||||||
<gf-value
|
<gf-value
|
||||||
class="align-items-end"
|
class="d-inline-block justify-content-end"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[value]="userItem.transactionCount"
|
[value]="userItem.transactionCount"
|
||||||
></gf-value>
|
></gf-value>
|
||||||
</td>
|
</td>
|
||||||
<td class="mat-cell px-1 py-2">
|
<td class="mat-cell px-1 py-2 text-right">
|
||||||
<gf-value
|
<gf-value
|
||||||
class="align-items-end"
|
class="d-inline-block justify-content-end"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[precision]="0"
|
[precision]="0"
|
||||||
[value]="userItem.engagement"
|
[value]="userItem.engagement"
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
<div class="row">
|
<div class="mb-2 row">
|
||||||
<div class="col-md-6 col-xs-12 d-flex">
|
<div class="col-md-6 col-xs-12 d-flex">
|
||||||
<div class="align-items-center d-flex flex-grow-1 h5 mb-0 text-truncate">
|
<div class="align-items-center d-flex flex-grow-1 h5 mb-0 text-truncate">
|
||||||
<span i18n>Benchmarks</span>
|
<span i18n>Performance</span>
|
||||||
<sup i18n>Beta</sup>
|
|
||||||
<gf-premium-indicator
|
<gf-premium-indicator
|
||||||
*ngIf="user?.subscription?.type === 'Basic'"
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
class="ml-1"
|
class="ml-1"
|
||||||
@ -14,6 +13,7 @@
|
|||||||
appearance="outline"
|
appearance="outline"
|
||||||
class="w-100 without-hint"
|
class="w-100 without-hint"
|
||||||
color="accent"
|
color="accent"
|
||||||
|
[hidden]="benchmarks?.length === 0"
|
||||||
>
|
>
|
||||||
<mat-label i18n>Compare with...</mat-label>
|
<mat-label i18n>Compare with...</mat-label>
|
||||||
<mat-select
|
<mat-select
|
||||||
@ -21,6 +21,7 @@
|
|||||||
[value]="benchmark"
|
[value]="benchmark"
|
||||||
(selectionChange)="onChangeBenchmark($event.value)"
|
(selectionChange)="onChangeBenchmark($event.value)"
|
||||||
>
|
>
|
||||||
|
<mat-option [value]="null"></mat-option>
|
||||||
<mat-option
|
<mat-option
|
||||||
*ngFor="let symbolProfile of benchmarks"
|
*ngFor="let symbolProfile of benchmarks"
|
||||||
[value]="symbolProfile.id"
|
[value]="symbolProfile.id"
|
||||||
@ -30,14 +31,6 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="user.settings.viewMode !== 'ZEN'" class="my-2 text-center">
|
|
||||||
<gf-toggle
|
|
||||||
[defaultValue]="user?.settings?.dateRange"
|
|
||||||
[isLoading]="isLoading"
|
|
||||||
[options]="dateRangeOptions"
|
|
||||||
(change)="onChangeDateRange($event.value)"
|
|
||||||
></gf-toggle>
|
|
||||||
</div>
|
|
||||||
<div class="chart-container">
|
<div class="chart-container">
|
||||||
<ngx-skeleton-loader
|
<ngx-skeleton-loader
|
||||||
*ngIf="isLoading"
|
*ngIf="isLoading"
|
||||||
|
@ -10,7 +10,6 @@ import {
|
|||||||
Output,
|
Output,
|
||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.component';
|
|
||||||
import {
|
import {
|
||||||
getTooltipOptions,
|
getTooltipOptions,
|
||||||
getTooltipPositionerMapTop,
|
getTooltipPositionerMapTop,
|
||||||
@ -24,7 +23,8 @@ import {
|
|||||||
parseDate
|
parseDate
|
||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
import { LineChartItem, User } from '@ghostfolio/common/interfaces';
|
import { LineChartItem, User } from '@ghostfolio/common/interfaces';
|
||||||
import { DateRange } from '@ghostfolio/common/types';
|
import { ColorScheme } from '@ghostfolio/common/types';
|
||||||
|
import { SymbolProfile } from '@prisma/client';
|
||||||
import {
|
import {
|
||||||
Chart,
|
Chart,
|
||||||
LineController,
|
LineController,
|
||||||
@ -35,7 +35,6 @@ import {
|
|||||||
Tooltip
|
Tooltip
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import annotationPlugin from 'chartjs-plugin-annotation';
|
import annotationPlugin from 'chartjs-plugin-annotation';
|
||||||
import { SymbolProfile } from '@prisma/client';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-benchmark-comparator',
|
selector: 'gf-benchmark-comparator',
|
||||||
@ -47,6 +46,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
@Input() benchmarkDataItems: LineChartItem[] = [];
|
@Input() benchmarkDataItems: LineChartItem[] = [];
|
||||||
@Input() benchmark: string;
|
@Input() benchmark: string;
|
||||||
@Input() benchmarks: Partial<SymbolProfile>[];
|
@Input() benchmarks: Partial<SymbolProfile>[];
|
||||||
|
@Input() colorScheme: ColorScheme;
|
||||||
@Input() daysInMarket: number;
|
@Input() daysInMarket: number;
|
||||||
@Input() isLoading: boolean;
|
@Input() isLoading: boolean;
|
||||||
@Input() locale: string;
|
@Input() locale: string;
|
||||||
@ -54,12 +54,10 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
@Input() user: User;
|
@Input() user: User;
|
||||||
|
|
||||||
@Output() benchmarkChanged = new EventEmitter<string>();
|
@Output() benchmarkChanged = new EventEmitter<string>();
|
||||||
@Output() dateRangeChanged = new EventEmitter<DateRange>();
|
|
||||||
|
|
||||||
@ViewChild('chartCanvas') chartCanvas;
|
@ViewChild('chartCanvas') chartCanvas;
|
||||||
|
|
||||||
public chart: Chart<any>;
|
public chart: Chart<any>;
|
||||||
public dateRangeOptions = ToggleComponent.DEFAULT_DATE_RANGE_OPTIONS;
|
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
Chart.register(
|
Chart.register(
|
||||||
@ -86,10 +84,6 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
this.benchmarkChanged.next(symbolProfileId);
|
this.benchmarkChanged.next(symbolProfileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onChangeDateRange(dateRange: DateRange) {
|
|
||||||
this.dateRangeChanged.next(dateRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
this.chart?.destroy();
|
this.chart?.destroy();
|
||||||
}
|
}
|
||||||
@ -135,7 +129,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
tension: 0
|
tension: 0
|
||||||
},
|
},
|
||||||
point: {
|
point: {
|
||||||
hoverBackgroundColor: getBackgroundColor(),
|
hoverBackgroundColor: getBackgroundColor(this.colorScheme),
|
||||||
hoverRadius: 2,
|
hoverRadius: 2,
|
||||||
radius: 0
|
radius: 0
|
||||||
}
|
}
|
||||||
@ -146,7 +140,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
annotation: {
|
annotation: {
|
||||||
annotations: {
|
annotations: {
|
||||||
yAxis: {
|
yAxis: {
|
||||||
borderColor: `rgba(${getTextColor()}, 0.1)`,
|
borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
scaleID: 'y',
|
scaleID: 'y',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
@ -159,7 +153,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
},
|
},
|
||||||
tooltip: this.getTooltipPluginConfiguration(),
|
tooltip: this.getTooltipPluginConfiguration(),
|
||||||
verticalHoverLine: {
|
verticalHoverLine: {
|
||||||
color: `rgba(${getTextColor()}, 0.1)`
|
color: `rgba(${getTextColor(this.colorScheme)}, 0.1)`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responsive: true,
|
responsive: true,
|
||||||
@ -167,9 +161,9 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
x: {
|
x: {
|
||||||
display: true,
|
display: true,
|
||||||
grid: {
|
grid: {
|
||||||
borderColor: `rgba(${getTextColor()}, 0.1)`,
|
borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
color: `rgba(${getTextColor()}, 0.8)`,
|
color: `rgba(${getTextColor(this.colorScheme)}, 0.8)`,
|
||||||
display: false
|
display: false
|
||||||
},
|
},
|
||||||
type: 'time',
|
type: 'time',
|
||||||
@ -181,8 +175,8 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
y: {
|
y: {
|
||||||
display: true,
|
display: true,
|
||||||
grid: {
|
grid: {
|
||||||
borderColor: `rgba(${getTextColor()}, 0.1)`,
|
borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
|
||||||
color: `rgba(${getTextColor()}, 0.8)`,
|
color: `rgba(${getTextColor(this.colorScheme)}, 0.8)`,
|
||||||
display: false,
|
display: false,
|
||||||
drawBorder: false
|
drawBorder: false
|
||||||
},
|
},
|
||||||
@ -198,7 +192,9 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [getVerticalHoverLinePlugin(this.chartCanvas)],
|
plugins: [
|
||||||
|
getVerticalHoverLinePlugin(this.chartCanvas, this.colorScheme)
|
||||||
|
],
|
||||||
type: 'line'
|
type: 'line'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -208,6 +204,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
private getTooltipPluginConfiguration() {
|
private getTooltipPluginConfiguration() {
|
||||||
return {
|
return {
|
||||||
...getTooltipOptions({
|
...getTooltipOptions({
|
||||||
|
colorScheme: this.colorScheme,
|
||||||
locale: this.locale,
|
locale: this.locale,
|
||||||
unit: '%'
|
unit: '%'
|
||||||
}),
|
}),
|
||||||
|
@ -2,7 +2,6 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
|
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
import { BenchmarkComparatorComponent } from './benchmark-comparator.component';
|
import { BenchmarkComparatorComponent } from './benchmark-comparator.component';
|
||||||
@ -13,7 +12,6 @@ import { BenchmarkComparatorComponent } from './benchmark-comparator.component';
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
GfToggleModule,
|
|
||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
NgxSkeletonLoaderModule,
|
NgxSkeletonLoaderModule,
|
||||||
ReactiveFormsModule
|
ReactiveFormsModule
|
||||||
|
@ -127,6 +127,7 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
|||||||
dataSource,
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
baseCurrency: this.user?.settings?.baseCurrency,
|
baseCurrency: this.user?.settings?.baseCurrency,
|
||||||
|
colorScheme: this.user?.settings?.colorScheme,
|
||||||
deviceType: this.deviceType,
|
deviceType: this.deviceType,
|
||||||
hasImpersonationId: this.hasImpersonationId,
|
hasImpersonationId: this.hasImpersonationId,
|
||||||
hasPermissionToReportDataGlitch: hasPermission(
|
hasPermissionToReportDataGlitch: hasPermission(
|
||||||
|
@ -24,7 +24,7 @@ export class HomeMarketComponent implements OnDestroy, OnInit {
|
|||||||
public fearLabel = $localize`Fear`;
|
public fearLabel = $localize`Fear`;
|
||||||
public greedLabel = $localize`Greed`;
|
public greedLabel = $localize`Greed`;
|
||||||
public hasPermissionToAccessFearAndGreedIndex: boolean;
|
public hasPermissionToAccessFearAndGreedIndex: boolean;
|
||||||
public historicalData: HistoricalDataItem[];
|
public historicalDataItems: HistoricalDataItem[];
|
||||||
public info: InfoItem;
|
public info: InfoItem;
|
||||||
public isLoading = true;
|
public isLoading = true;
|
||||||
public readonly numberOfDays = 180;
|
public readonly numberOfDays = 180;
|
||||||
@ -67,7 +67,7 @@ export class HomeMarketComponent implements OnDestroy, OnInit {
|
|||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(({ historicalData, marketPrice }) => {
|
.subscribe(({ historicalData, marketPrice }) => {
|
||||||
this.fearAndGreedIndex = marketPrice;
|
this.fearAndGreedIndex = marketPrice;
|
||||||
this.historicalData = [
|
this.historicalDataItems = [
|
||||||
...historicalData,
|
...historicalData,
|
||||||
{
|
{
|
||||||
date: resetHours(new Date()).toISOString(),
|
date: resetHours(new Date()).toISOString(),
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
symbol="Fear & Greed Index"
|
symbol="Fear & Greed Index"
|
||||||
yMax="100"
|
yMax="100"
|
||||||
yMin="0"
|
yMin="0"
|
||||||
[historicalDataItems]="historicalData"
|
[historicalDataItems]="historicalDataItems"
|
||||||
|
[isAnimated]="true"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[showXAxis]="true"
|
[showXAxis]="true"
|
||||||
[showYAxis]="true"
|
[showYAxis]="true"
|
||||||
|
@ -107,8 +107,7 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchPortfolioPerformance({
|
.fetchPortfolioPerformance({
|
||||||
range: this.user?.settings?.dateRange,
|
range: this.user?.settings?.dateRange
|
||||||
version: this.user?.settings?.isExperimentalFeatures ? 2 : 1
|
|
||||||
})
|
})
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((response) => {
|
.subscribe((response) => {
|
||||||
@ -117,35 +116,14 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
|
|||||||
this.performance = response.performance;
|
this.performance = response.performance;
|
||||||
this.isLoadingPerformance = false;
|
this.isLoadingPerformance = false;
|
||||||
|
|
||||||
if (this.user?.settings?.isExperimentalFeatures) {
|
this.historicalDataItems = response.chart.map(
|
||||||
this.historicalDataItems = response.chart.map(({ date, value }) => {
|
({ date, netPerformanceInPercentage }) => {
|
||||||
return {
|
return {
|
||||||
date,
|
date,
|
||||||
value
|
value: netPerformanceInPercentage
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
} else {
|
);
|
||||||
this.dataService
|
|
||||||
.fetchChart({
|
|
||||||
range: this.user?.settings?.dateRange,
|
|
||||||
version: 1
|
|
||||||
})
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((chartData) => {
|
|
||||||
this.historicalDataItems = chartData.chart.map(
|
|
||||||
({ date, value }) => {
|
|
||||||
return {
|
|
||||||
date,
|
|
||||||
value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
this.isAllTimeHigh = chartData.isAllTimeHigh;
|
|
||||||
this.isAllTimeLow = chartData.isAllTimeLow;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
@ -15,16 +15,17 @@
|
|||||||
<gf-line-chart
|
<gf-line-chart
|
||||||
class="position-absolute"
|
class="position-absolute"
|
||||||
symbol="Performance"
|
symbol="Performance"
|
||||||
[currency]="user?.settings?.isExperimentalFeatures ? undefined : user?.settings?.baseCurrency"
|
unit="%"
|
||||||
[historicalDataItems]="historicalDataItems"
|
[colorScheme]="user?.settings?.colorScheme"
|
||||||
[hidden]="historicalDataItems?.length === 0"
|
[hidden]="historicalDataItems?.length === 0"
|
||||||
|
[historicalDataItems]="historicalDataItems"
|
||||||
|
[isAnimated]="user?.settings?.dateRange === '1d' ? false : true"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[ngClass]="{ 'pr-3': deviceType === 'mobile' }"
|
[ngClass]="{ 'pr-3': deviceType === 'mobile' }"
|
||||||
[showGradient]="true"
|
[showGradient]="true"
|
||||||
[showLoader]="false"
|
[showLoader]="false"
|
||||||
[showXAxis]="false"
|
[showXAxis]="false"
|
||||||
[showYAxis]="false"
|
[showYAxis]="false"
|
||||||
[unit]="user?.settings?.isExperimentalFeatures ? '%' : undefined"
|
|
||||||
></gf-line-chart>
|
></gf-line-chart>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,14 +15,16 @@ import {
|
|||||||
} from '@ghostfolio/common/chart-helper';
|
} from '@ghostfolio/common/chart-helper';
|
||||||
import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
|
import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
|
||||||
import {
|
import {
|
||||||
|
DATE_FORMAT,
|
||||||
getBackgroundColor,
|
getBackgroundColor,
|
||||||
getDateFormatString,
|
getDateFormatString,
|
||||||
getTextColor,
|
getTextColor,
|
||||||
parseDate,
|
parseDate,
|
||||||
transformTickToAbbreviation
|
transformTickToAbbreviation
|
||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
|
import { LineChartItem } from '@ghostfolio/common/interfaces';
|
||||||
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
||||||
import { GroupBy } from '@ghostfolio/common/types';
|
import { ColorScheme, DateRange, GroupBy } from '@ghostfolio/common/types';
|
||||||
import {
|
import {
|
||||||
BarController,
|
BarController,
|
||||||
BarElement,
|
BarElement,
|
||||||
@ -35,7 +37,7 @@ import {
|
|||||||
Tooltip
|
Tooltip
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import annotationPlugin from 'chartjs-plugin-annotation';
|
import annotationPlugin from 'chartjs-plugin-annotation';
|
||||||
import { addDays, isAfter, parseISO, subDays } from 'date-fns';
|
import { addDays, format, isAfter, parseISO, subDays } from 'date-fns';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-investment-chart',
|
selector: 'gf-investment-chart',
|
||||||
@ -44,19 +46,21 @@ import { addDays, isAfter, parseISO, subDays } from 'date-fns';
|
|||||||
styleUrls: ['./investment-chart.component.scss']
|
styleUrls: ['./investment-chart.component.scss']
|
||||||
})
|
})
|
||||||
export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
||||||
|
@Input() benchmarkDataItems: InvestmentItem[] = [];
|
||||||
|
@Input() colorScheme: ColorScheme;
|
||||||
@Input() currency: string;
|
@Input() currency: string;
|
||||||
@Input() daysInMarket: number;
|
@Input() daysInMarket: number;
|
||||||
@Input() groupBy: GroupBy;
|
@Input() groupBy: GroupBy;
|
||||||
@Input() investments: InvestmentItem[];
|
@Input() historicalDataItems: LineChartItem[] = [];
|
||||||
@Input() isInPercent = false;
|
@Input() isInPercent = false;
|
||||||
|
@Input() isLoading = false;
|
||||||
@Input() locale: string;
|
@Input() locale: string;
|
||||||
|
@Input() range: DateRange = 'max';
|
||||||
@Input() savingsRate = 0;
|
@Input() savingsRate = 0;
|
||||||
|
|
||||||
@ViewChild('chartCanvas') chartCanvas;
|
@ViewChild('chartCanvas') chartCanvas;
|
||||||
|
|
||||||
public chart: Chart;
|
public chart: Chart<any>;
|
||||||
public isLoading = true;
|
|
||||||
|
|
||||||
private data: InvestmentItem[];
|
private data: InvestmentItem[];
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
@ -77,7 +81,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ngOnChanges() {
|
public ngOnChanges() {
|
||||||
if (this.investments) {
|
if (this.benchmarkDataItems && this.historicalDataItems) {
|
||||||
this.initialize();
|
this.initialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,58 +91,72 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private initialize() {
|
private initialize() {
|
||||||
this.isLoading = true;
|
|
||||||
|
|
||||||
// Create a clone
|
// Create a clone
|
||||||
this.data = this.investments.map((a) => Object.assign({}, a));
|
this.data = this.benchmarkDataItems.map((item) => Object.assign({}, item));
|
||||||
|
|
||||||
if (!this.groupBy && this.data?.length > 0) {
|
if (!this.groupBy && this.data?.length > 0) {
|
||||||
// Extend chart by 5% of days in market (before)
|
if (this.range === 'max') {
|
||||||
const firstItem = this.data[0];
|
// Extend chart by 5% of days in market (before)
|
||||||
this.data.unshift({
|
const firstItem = this.data[0];
|
||||||
...firstItem,
|
this.data.unshift({
|
||||||
date: subDays(
|
...firstItem,
|
||||||
parseISO(firstItem.date),
|
date: format(
|
||||||
this.daysInMarket * 0.05 || 90
|
subDays(parseISO(firstItem.date), this.daysInMarket * 0.05 || 90),
|
||||||
).toISOString(),
|
DATE_FORMAT
|
||||||
investment: 0
|
),
|
||||||
});
|
investment: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Extend chart by 5% of days in market (after)
|
// Extend chart by 5% of days in market (after)
|
||||||
const lastItem = this.data[this.data.length - 1];
|
const lastItem = this.data[this.data.length - 1];
|
||||||
this.data.push({
|
this.data.push({
|
||||||
...lastItem,
|
...lastItem,
|
||||||
date: addDays(
|
date: format(
|
||||||
parseDate(lastItem.date),
|
addDays(parseDate(lastItem.date), this.daysInMarket * 0.05 || 90),
|
||||||
this.daysInMarket * 0.05 || 90
|
DATE_FORMAT
|
||||||
).toISOString()
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
labels: this.data.map((investmentItem) => {
|
labels: this.historicalDataItems.map(({ date }) => {
|
||||||
return investmentItem.date;
|
return parseDate(date);
|
||||||
}),
|
}),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`,
|
backgroundColor: `rgb(${secondaryColorRgb.r}, ${secondaryColorRgb.g}, ${secondaryColorRgb.b})`,
|
||||||
borderColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`,
|
borderColor: `rgb(${secondaryColorRgb.r}, ${secondaryColorRgb.g}, ${secondaryColorRgb.b})`,
|
||||||
borderWidth: this.groupBy ? 0 : 2,
|
borderWidth: this.groupBy ? 0 : 1,
|
||||||
data: this.data.map((position) => {
|
data: this.data.map(({ date, investment }) => {
|
||||||
return this.isInPercent
|
return {
|
||||||
? position.investment * 100
|
x: parseDate(date),
|
||||||
: position.investment;
|
y: this.isInPercent ? investment * 100 : investment
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
label: $localize`Deposit`,
|
label: $localize`Deposit`,
|
||||||
segment: {
|
segment: {
|
||||||
borderColor: (context: unknown) =>
|
borderColor: (context: unknown) =>
|
||||||
this.isInFuture(
|
this.isInFuture(
|
||||||
context,
|
context,
|
||||||
`rgba(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b}, 0.67)`
|
`rgba(${secondaryColorRgb.r}, ${secondaryColorRgb.g}, ${secondaryColorRgb.b}, 0.67)`
|
||||||
),
|
),
|
||||||
borderDash: (context: unknown) => this.isInFuture(context, [2, 2])
|
borderDash: (context: unknown) => this.isInFuture(context, [2, 2])
|
||||||
},
|
},
|
||||||
stepped: true
|
stepped: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
borderColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`,
|
||||||
|
borderWidth: 2,
|
||||||
|
data: this.historicalDataItems.map(({ date, value }) => {
|
||||||
|
return {
|
||||||
|
x: parseDate(date),
|
||||||
|
y: this.isInPercent ? value * 100 : value
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
fill: false,
|
||||||
|
label: $localize`Total Amount`,
|
||||||
|
pointRadius: 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -160,7 +178,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
tension: 0
|
tension: 0
|
||||||
},
|
},
|
||||||
point: {
|
point: {
|
||||||
hoverBackgroundColor: getBackgroundColor(),
|
hoverBackgroundColor: getBackgroundColor(this.colorScheme),
|
||||||
hoverRadius: 2,
|
hoverRadius: 2,
|
||||||
radius: 0
|
radius: 0
|
||||||
}
|
}
|
||||||
@ -172,13 +190,13 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
annotations: {
|
annotations: {
|
||||||
savingsRate: this.savingsRate
|
savingsRate: this.savingsRate
|
||||||
? {
|
? {
|
||||||
borderColor: `rgba(${secondaryColorRgb.r}, ${secondaryColorRgb.g}, ${secondaryColorRgb.b}, 0.75)`,
|
borderColor: `rgba(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b}, 0.75)`,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
label: {
|
label: {
|
||||||
backgroundColor: `rgb(${secondaryColorRgb.r}, ${secondaryColorRgb.g}, ${secondaryColorRgb.b})`,
|
backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`,
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
color: 'white',
|
color: 'white',
|
||||||
content: 'Savings Rate',
|
content: $localize`Savings Rate`,
|
||||||
display: true,
|
display: true,
|
||||||
font: { size: '10px', weight: 'normal' },
|
font: { size: '10px', weight: 'normal' },
|
||||||
padding: {
|
padding: {
|
||||||
@ -193,7 +211,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
yAxis: {
|
yAxis: {
|
||||||
borderColor: `rgba(${getTextColor()}, 0.1)`,
|
borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
scaleID: 'y',
|
scaleID: 'y',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
@ -206,7 +224,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
},
|
},
|
||||||
tooltip: this.getTooltipPluginConfiguration(),
|
tooltip: this.getTooltipPluginConfiguration(),
|
||||||
verticalHoverLine: {
|
verticalHoverLine: {
|
||||||
color: `rgba(${getTextColor()}, 0.1)`
|
color: `rgba(${getTextColor(this.colorScheme)}, 0.1)`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responsive: true,
|
responsive: true,
|
||||||
@ -214,9 +232,9 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
x: {
|
x: {
|
||||||
display: true,
|
display: true,
|
||||||
grid: {
|
grid: {
|
||||||
borderColor: `rgba(${getTextColor()}, 0.1)`,
|
borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
|
||||||
borderWidth: this.groupBy ? 0 : 1,
|
borderWidth: this.groupBy ? 0 : 1,
|
||||||
color: `rgba(${getTextColor()}, 0.8)`,
|
color: `rgba(${getTextColor(this.colorScheme)}, 0.8)`,
|
||||||
display: false
|
display: false
|
||||||
},
|
},
|
||||||
type: 'time',
|
type: 'time',
|
||||||
@ -228,8 +246,8 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
y: {
|
y: {
|
||||||
display: !this.isInPercent,
|
display: !this.isInPercent,
|
||||||
grid: {
|
grid: {
|
||||||
borderColor: `rgba(${getTextColor()}, 0.1)`,
|
borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
|
||||||
color: `rgba(${getTextColor()}, 0.8)`,
|
color: `rgba(${getTextColor(this.colorScheme)}, 0.8)`,
|
||||||
display: false,
|
display: false,
|
||||||
drawBorder: false
|
drawBorder: false
|
||||||
},
|
},
|
||||||
@ -245,18 +263,19 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [getVerticalHoverLinePlugin(this.chartCanvas)],
|
plugins: [
|
||||||
|
getVerticalHoverLinePlugin(this.chartCanvas, this.colorScheme)
|
||||||
|
],
|
||||||
type: this.groupBy ? 'bar' : 'line'
|
type: this.groupBy ? 'bar' : 'line'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTooltipPluginConfiguration() {
|
private getTooltipPluginConfiguration() {
|
||||||
return {
|
return {
|
||||||
...getTooltipOptions({
|
...getTooltipOptions({
|
||||||
|
colorScheme: this.colorScheme,
|
||||||
currency: this.isInPercent ? undefined : this.currency,
|
currency: this.isInPercent ? undefined : this.currency,
|
||||||
locale: this.isInPercent ? undefined : this.locale,
|
locale: this.isInPercent ? undefined : this.locale,
|
||||||
unit: this.isInPercent ? '%' : undefined
|
unit: this.isInPercent ? '%' : undefined
|
||||||
|
@ -22,9 +22,6 @@
|
|||||||
<div class="row px-3 py-1">
|
<div class="row px-3 py-1">
|
||||||
<div class="d-flex flex-grow-1" i18n>Sell</div>
|
<div class="d-flex flex-grow-1" i18n>Sell</div>
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
<span *ngIf="summary?.totalSell || summary?.totalSell === 0" class="mr-1"
|
|
||||||
>-</span
|
|
||||||
>
|
|
||||||
<gf-value
|
<gf-value
|
||||||
class="justify-content-end"
|
class="justify-content-end"
|
||||||
[currency]="baseCurrency"
|
[currency]="baseCurrency"
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
import { ColorScheme } from '@ghostfolio/common/types';
|
||||||
import { DataSource } from '@prisma/client';
|
import { DataSource } from '@prisma/client';
|
||||||
|
|
||||||
export interface PositionDetailDialogParams {
|
export interface PositionDetailDialogParams {
|
||||||
baseCurrency: string;
|
baseCurrency: string;
|
||||||
|
colorScheme: ColorScheme;
|
||||||
dataSource: DataSource;
|
dataSource: DataSource;
|
||||||
deviceType: string;
|
deviceType: string;
|
||||||
hasImpersonationId: boolean;
|
hasImpersonationId: boolean;
|
||||||
|
@ -20,11 +20,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<gf-line-chart
|
<gf-line-chart
|
||||||
class="mb-4"
|
|
||||||
benchmarkLabel="Average Unit Price"
|
benchmarkLabel="Average Unit Price"
|
||||||
|
class="mb-4"
|
||||||
[benchmarkDataItems]="benchmarkDataItems"
|
[benchmarkDataItems]="benchmarkDataItems"
|
||||||
|
[colorScheme]="data.colorScheme"
|
||||||
[currency]="SymbolProfile?.currency"
|
[currency]="SymbolProfile?.currency"
|
||||||
[historicalDataItems]="historicalDataItems"
|
[historicalDataItems]="historicalDataItems"
|
||||||
|
[isAnimated]="true"
|
||||||
[locale]="data.locale"
|
[locale]="data.locale"
|
||||||
[showGradient]="true"
|
[showGradient]="true"
|
||||||
[showXAxis]="true"
|
[showXAxis]="true"
|
||||||
@ -187,6 +189,7 @@
|
|||||||
<div class="h5" i18n>Sectors</div>
|
<div class="h5" i18n>Sectors</div>
|
||||||
<gf-portfolio-proportion-chart
|
<gf-portfolio-proportion-chart
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[colorScheme]="data.colorScheme"
|
||||||
[isInPercent]="true"
|
[isInPercent]="true"
|
||||||
[keys]="['name']"
|
[keys]="['name']"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
@ -198,6 +201,7 @@
|
|||||||
<div class="h5" i18n>Countries</div>
|
<div class="h5" i18n>Countries</div>
|
||||||
<gf-portfolio-proportion-chart
|
<gf-portfolio-proportion-chart
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[colorScheme]="data.colorScheme"
|
||||||
[isInPercent]="true"
|
[isInPercent]="true"
|
||||||
[keys]="['name']"
|
[keys]="['name']"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
Router,
|
Router,
|
||||||
RouterStateSnapshot
|
RouterStateSnapshot
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { SettingsStorageService } from '@ghostfolio/client/services/settings-storage.service';
|
import { SettingsStorageService } from '@ghostfolio/client/services/settings-storage.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { EMPTY } from 'rxjs';
|
import { EMPTY } from 'rxjs';
|
||||||
@ -30,6 +31,7 @@ export class AuthGuard implements CanActivate {
|
|||||||
];
|
];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private dataService: DataService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private settingsStorageService: SettingsStorageService,
|
private settingsStorageService: SettingsStorageService,
|
||||||
private userService: UserService
|
private userService: UserService
|
||||||
@ -74,8 +76,17 @@ export class AuthGuard implements CanActivate {
|
|||||||
const userLanguage = user?.settings?.language;
|
const userLanguage = user?.settings?.language;
|
||||||
|
|
||||||
if (userLanguage && document.documentElement.lang !== userLanguage) {
|
if (userLanguage && document.documentElement.lang !== userLanguage) {
|
||||||
window.location.href = `../${userLanguage}`;
|
this.dataService
|
||||||
resolve(false);
|
.putUserSetting({ language: document.documentElement.lang })
|
||||||
|
.subscribe(() => {
|
||||||
|
this.userService.remove();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(true);
|
||||||
return;
|
return;
|
||||||
} else if (
|
} else if (
|
||||||
state.url.startsWith('/home') &&
|
state.url.startsWith('/home') &&
|
||||||
|
@ -177,7 +177,7 @@
|
|||||||
<a
|
<a
|
||||||
class="py-2 w-100"
|
class="py-2 w-100"
|
||||||
color="primary"
|
color="primary"
|
||||||
mat-stroked-button
|
mat-flat-button
|
||||||
[routerLink]="['/faq']"
|
[routerLink]="['/faq']"
|
||||||
>FAQ</a
|
>FAQ</a
|
||||||
>
|
>
|
||||||
@ -189,7 +189,7 @@
|
|||||||
<a
|
<a
|
||||||
class="py-2 w-100"
|
class="py-2 w-100"
|
||||||
color="primary"
|
color="primary"
|
||||||
mat-stroked-button
|
mat-flat-button
|
||||||
[routerLink]="['/about', 'changelog']"
|
[routerLink]="['/about', 'changelog']"
|
||||||
>Changelog & License</a
|
>Changelog & License</a
|
||||||
>
|
>
|
||||||
@ -198,7 +198,7 @@
|
|||||||
<a
|
<a
|
||||||
class="py-2 w-100"
|
class="py-2 w-100"
|
||||||
color="primary"
|
color="primary"
|
||||||
mat-stroked-button
|
mat-flat-button
|
||||||
[routerLink]="['/about', 'privacy-policy']"
|
[routerLink]="['/about', 'privacy-policy']"
|
||||||
>Privacy Policy</a
|
>Privacy Policy</a
|
||||||
>
|
>
|
||||||
|
@ -42,6 +42,7 @@ export class AccountPageComponent implements OnDestroy, OnInit {
|
|||||||
signInWithFingerprintElement: MatSlideToggle;
|
signInWithFingerprintElement: MatSlideToggle;
|
||||||
|
|
||||||
public accesses: Access[];
|
public accesses: Access[];
|
||||||
|
public appearancePlaceholder = $localize`Auto`;
|
||||||
public baseCurrency: string;
|
public baseCurrency: string;
|
||||||
public coupon: number;
|
public coupon: number;
|
||||||
public couponId: string;
|
public couponId: string;
|
||||||
|
@ -167,7 +167,7 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex">
|
<div class="d-flex mb-2">
|
||||||
<div class="align-items-center d-flex pr-1 pt-1 w-50">
|
<div class="align-items-center d-flex pr-1 pt-1 w-50">
|
||||||
<ng-container i18n>View Mode</ng-container>
|
<ng-container i18n>View Mode</ng-container>
|
||||||
</div>
|
</div>
|
||||||
@ -190,6 +190,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="align-items-center d-flex pr-1 pt-1 w-50">
|
||||||
|
<ng-container i18n>Appearance</ng-container>
|
||||||
|
</div>
|
||||||
|
<div class="pl-1 w-50">
|
||||||
|
<mat-form-field
|
||||||
|
appearance="outline"
|
||||||
|
class="compact-with-outline w-100 without-hint"
|
||||||
|
>
|
||||||
|
<mat-select
|
||||||
|
class="with-placeholder-as-option"
|
||||||
|
name="colorScheme"
|
||||||
|
[disabled]="!hasPermissionToUpdateUserSettings"
|
||||||
|
[placeholder]="appearancePlaceholder"
|
||||||
|
[value]="user?.settings?.colorScheme"
|
||||||
|
(selectionChange)="onChangeUserSetting('colorScheme', $event.value)"
|
||||||
|
>
|
||||||
|
<mat-option i18n [value]="null">Auto</mat-option>
|
||||||
|
<mat-option i18n value="LIGHT">Light</mat-option>
|
||||||
|
<mat-option i18n value="DARK">Dark</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="align-items-center d-flex mt-4 py-1">
|
<div class="align-items-center d-flex mt-4 py-1">
|
||||||
@ -243,8 +267,8 @@
|
|||||||
class="align-items-center d-flex justify-content-center"
|
class="align-items-center d-flex justify-content-center"
|
||||||
color="primary"
|
color="primary"
|
||||||
mat-fab
|
mat-fab
|
||||||
[routerLink]="[]"
|
|
||||||
[queryParams]="{ createDialog: true }"
|
[queryParams]="{ createDialog: true }"
|
||||||
|
[routerLink]="[]"
|
||||||
>
|
>
|
||||||
<ion-icon name="add-outline" size="large"></ion-icon>
|
<ion-icon name="add-outline" size="large"></ion-icon>
|
||||||
</a>
|
</a>
|
||||||
|
@ -119,7 +119,7 @@
|
|||||||
Anlagestrategie? Ich freue mich über alle, die Ghostfolio
|
Anlagestrategie? Ich freue mich über alle, die Ghostfolio
|
||||||
ausprobieren. Bist du überzeugt vom Potential der Software? Jede
|
ausprobieren. Bist du überzeugt vom Potential der Software? Jede
|
||||||
Unterstützung für Ghostfolio ist willkommen. Sei es mit einer
|
Unterstützung für Ghostfolio ist willkommen. Sei es mit einer
|
||||||
<a href="https://ghostfol.io/pricing">Ghostfolio Premium</a>
|
<a [routerLink]="['/pricing']">Ghostfolio Premium</a>
|
||||||
Subscription zur Finanzierung des Hostings, einem positiven Rating
|
Subscription zur Finanzierung des Hostings, einem positiven Rating
|
||||||
im
|
im
|
||||||
<a
|
<a
|
||||||
|
@ -115,7 +115,7 @@
|
|||||||
strategy? I'm happy for everyone who tries Ghostfolio. Are you
|
strategy? I'm happy for everyone who tries Ghostfolio. Are you
|
||||||
convinced of its potential? Any support for Ghostfolio is welcome.
|
convinced of its potential? Any support for Ghostfolio is welcome.
|
||||||
Be it with a
|
Be it with a
|
||||||
<a href="https://ghostfol.io/pricing">Ghostfolio Premium</a>
|
<a [routerLink]="['/pricing']">Ghostfolio Premium</a>
|
||||||
Subscription to finance the hosting, a positive rating in the
|
Subscription to finance the hosting, a positive rating in the
|
||||||
<a
|
<a
|
||||||
href="https://play.google.com/store/apps/details?id=ch.dotsilver.ghostfolio.twa"
|
href="https://play.google.com/store/apps/details?id=ch.dotsilver.ghostfolio.twa"
|
||||||
|
@ -74,7 +74,7 @@
|
|||||||
<a [routerLink]="['/markets']">economic situation</a> at this time,
|
<a [routerLink]="['/markets']">economic situation</a> at this time,
|
||||||
the goal set at the beginning of the year to build a sustainable
|
the goal set at the beginning of the year to build a sustainable
|
||||||
business and reach break-even with the SaaS offering (<a
|
business and reach break-even with the SaaS offering (<a
|
||||||
[routerLink]="['/markets']"
|
[routerLink]="['/pricing']"
|
||||||
>Ghostfolio Premium</a
|
>Ghostfolio Premium</a
|
||||||
>) has been achieved. We will continue to leverage the revenue to
|
>) has been achieved. We will continue to leverage the revenue to
|
||||||
further improve the fully managed cloud offering for our paying
|
further improve the fully managed cloud offering for our paying
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||||
|
|
||||||
|
import { Hacktoberfest2022PageComponent } from './hacktoberfest-2022-page.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
canActivate: [AuthGuard],
|
||||||
|
component: Hacktoberfest2022PageComponent,
|
||||||
|
path: '',
|
||||||
|
title: 'Hacktoberfest 2022'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class Hacktoberfest2022RoutingModule {}
|
@ -0,0 +1,9 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
host: { class: 'page' },
|
||||||
|
selector: 'gf-hacktoberfest-2022-page',
|
||||||
|
styleUrls: ['./hacktoberfest-2022-page.scss'],
|
||||||
|
templateUrl: './hacktoberfest-2022-page.html'
|
||||||
|
})
|
||||||
|
export class Hacktoberfest2022PageComponent {}
|
@ -0,0 +1,178 @@
|
|||||||
|
<div class="blog container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 offset-md-2">
|
||||||
|
<article>
|
||||||
|
<div class="mb-4 text-center">
|
||||||
|
<h1 class="mb-1">Hacktoberfest 2022</h1>
|
||||||
|
<div class="mb-3 text-muted"><small>2022-10-01</small></div>
|
||||||
|
<img
|
||||||
|
alt="Hacktoberfest 2022 with Ghostfolio Teaser"
|
||||||
|
class="rounded w-100"
|
||||||
|
src="../assets/images/blog/hacktoberfest-2022.png"
|
||||||
|
title="Hacktoberfest 2022 with Ghostfolio"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<section class="mb-4">
|
||||||
|
<p>
|
||||||
|
We are very excited to join
|
||||||
|
<a href="https://hacktoberfest.com">Hacktoberfest</a> for the first
|
||||||
|
time with <a href="https://ghostfol.io">Ghostfolio</a> and meet new
|
||||||
|
and ambitious open-source contributors. Hacktoberfest is a
|
||||||
|
month-long celebration of open-source projects, their maintainers,
|
||||||
|
and the entire community of contributors. Each October, open source
|
||||||
|
maintainers from all over the world give extra attention to new
|
||||||
|
contributors while guiding them through their first pull requests on
|
||||||
|
<a href="https://github.com/ghostfolio/ghostfolio">GitHub</a>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<section class="mb-4">
|
||||||
|
<h2 class="h4">About Ghostfolio</h2>
|
||||||
|
<p>
|
||||||
|
Ghostfolio is a modern web application to manage your personal
|
||||||
|
finance. The software presents the current assets in real time and
|
||||||
|
supports the decision making of future investments. Whether
|
||||||
|
rebalancing the asset classes (stocks, ETFs, cryptocurrencies, etc.)
|
||||||
|
of your portfolio or financing an apartment, Ghostfolio offers
|
||||||
|
solid, data-driven decision support.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Ghostfolio is written in
|
||||||
|
<a href="https://www.typescriptlang.org">TypeScript</a> and
|
||||||
|
organized as an <a href="https://nx.dev">Nx</a> workspace. The
|
||||||
|
backend is based on <a href="https://nestjs.com">NestJS</a> using
|
||||||
|
<a href="https://www.postgresql.org">PostgreSQL</a> as a database
|
||||||
|
together with <a href="https://www.prisma.io">Prisma</a> and
|
||||||
|
<a href="https://redis.io">Redis</a> for caching. The frontend is
|
||||||
|
built with <a href="https://angular.io">Angular</a>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<section class="mb-4">
|
||||||
|
<h2 class="h4">How to contribute?</h2>
|
||||||
|
<p>
|
||||||
|
Every contribution matters. This can be implementing new features,
|
||||||
|
fixing bugs, refactoring the code, improving the documentation,
|
||||||
|
adding more unit tests, or translating into another language.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Are you not yet familiar with our code base? That is not a problem.
|
||||||
|
We have applied the label <code>hacktoberfest</code> to a few
|
||||||
|
<a
|
||||||
|
href="https://github.com/ghostfolio/ghostfolio/issues?q=is%3Aissue+is%3Aopen+label%3Ahacktoberfest"
|
||||||
|
>issues</a
|
||||||
|
>
|
||||||
|
and
|
||||||
|
<a
|
||||||
|
href="https://github.com/ghostfolio/ghostfolio/discussions?discussions_q=label%3Ahacktoberfest"
|
||||||
|
>ideas</a
|
||||||
|
>
|
||||||
|
that are well suited for newcomers.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The official Hacktoberfest website provides some valuable
|
||||||
|
<a
|
||||||
|
href="https://hacktoberfest.com/participation/#beginner-resources"
|
||||||
|
>resources for beginners</a
|
||||||
|
>
|
||||||
|
to start contributing in open source.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<section class="mb-4">
|
||||||
|
<h2 class="h4">Get support</h2>
|
||||||
|
<p>
|
||||||
|
If you have further questions or ideas, please join our growing
|
||||||
|
<a href="https://ghostfolio.slack.com">Slack community</a> or get in
|
||||||
|
touch on Twitter
|
||||||
|
<a href="https://twitter.com/ghostfolio_">@ghostfolio_</a> or by
|
||||||
|
email via <a href="mailto:hi@ghostfol.io">hi@ghostfol.io</a>.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We look forward to hearing from you.<br />
|
||||||
|
Thomas from Ghostfolio
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
<section class="mb-4">
|
||||||
|
<ul class="list-inline">
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Angular</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Community</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Cryptocurrency</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">ETF</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Finance</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Fintech</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Ghostfolio</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">GitHub</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Hacktoberfest</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Investment</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">NestJS</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Nx</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">October</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Open Source</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">OSS</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Personal Finance</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Portfolio</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Portfolio Tracker</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Prisma</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Software</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Stock</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">TypeScript</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Wealth</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Wealth Management</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Web3</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-inline-item">
|
||||||
|
<span class="badge badge-light">Web 3.0</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,13 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
|
import { Hacktoberfest2022RoutingModule } from './hacktoberfest-2022-page-routing.module';
|
||||||
|
import { Hacktoberfest2022PageComponent } from './hacktoberfest-2022-page.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [Hacktoberfest2022PageComponent],
|
||||||
|
imports: [CommonModule, Hacktoberfest2022RoutingModule, RouterModule],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class Hacktoberfest2022PageModule {}
|
@ -0,0 +1,3 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
@ -2,6 +2,30 @@
|
|||||||
<div class="mb-5 row">
|
<div class="mb-5 row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3 class="mb-3 text-center" i18n>Blog</h3>
|
<h3 class="mb-3 text-center" i18n>Blog</h3>
|
||||||
|
<mat-card class="mb-3">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="container p-0">
|
||||||
|
<div class="flex-nowrap no-gutters row">
|
||||||
|
<a
|
||||||
|
class="d-flex w-100"
|
||||||
|
href="../en/blog/2022/10/hacktoberfest-2022"
|
||||||
|
>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<div class="h6 m-0 text-truncate">Hacktoberfest 2022</div>
|
||||||
|
<div class="d-flex text-muted">2022-10-01</div>
|
||||||
|
</div>
|
||||||
|
<div class="align-items-center d-flex">
|
||||||
|
<ion-icon
|
||||||
|
class="chevron text-muted"
|
||||||
|
name="chevron-forward-outline"
|
||||||
|
size="small"
|
||||||
|
></ion-icon>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
<mat-card class="mb-3">
|
<mat-card class="mb-3">
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<div class="container p-0">
|
<div class="container p-0">
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
|
import { Statistics } from '@ghostfolio/common/interfaces/statistics.interface';
|
||||||
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -11,6 +15,9 @@ import { Subject } from 'rxjs';
|
|||||||
export class LandingPageComponent implements OnDestroy, OnInit {
|
export class LandingPageComponent implements OnDestroy, OnInit {
|
||||||
public currentYear = format(new Date(), 'yyyy');
|
public currentYear = format(new Date(), 'yyyy');
|
||||||
public demoAuthToken: string;
|
public demoAuthToken: string;
|
||||||
|
public deviceType: string;
|
||||||
|
public hasPermissionForStatistics: boolean;
|
||||||
|
public statistics: Statistics;
|
||||||
public testimonials = [
|
public testimonials = [
|
||||||
{
|
{
|
||||||
author: 'Philipp',
|
author: 'Philipp',
|
||||||
@ -36,9 +43,23 @@ export class LandingPageComponent implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
public constructor() {}
|
public constructor(
|
||||||
|
private dataService: DataService,
|
||||||
|
private deviceService: DeviceDetectorService
|
||||||
|
) {
|
||||||
|
const { globalPermissions, statistics } = this.dataService.fetchInfo();
|
||||||
|
|
||||||
public ngOnInit() {}
|
this.hasPermissionForStatistics = hasPermission(
|
||||||
|
globalPermissions,
|
||||||
|
permissions.enableStatistics
|
||||||
|
);
|
||||||
|
|
||||||
|
this.statistics = statistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit() {
|
||||||
|
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
||||||
|
}
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
this.unsubscribeSubject.next();
|
this.unsubscribeSubject.next();
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<h1 class="font-weight-bold intro my-5">
|
<h1 class="font-weight-bold intro mt-5">
|
||||||
Manage your wealth like a boss
|
Manage your wealth like a boss
|
||||||
</h1>
|
</h1>
|
||||||
<div>
|
<p class="lead mb-4">
|
||||||
|
Ghostfolio is a privacy-first, open source dashboard for your personal
|
||||||
|
finances. Break down your asset allocation, know your net worth and make
|
||||||
|
solid, data-driven investment decisions.
|
||||||
|
</p>
|
||||||
|
<div class="mb-4">
|
||||||
<a
|
<a
|
||||||
href="https://www.youtube.com/watch?v=yY6ObSQVJZk"
|
href="https://www.youtube.com/watch?v=yY6ObSQVJZk"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -23,9 +28,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="button-container row">
|
<div class="button-container mb-5 row">
|
||||||
<div class="align-items-center col d-flex justify-content-center">
|
<div class="align-items-center col d-flex justify-content-center">
|
||||||
<div class="py-5 text-center">
|
<div class="text-center">
|
||||||
<a
|
<a
|
||||||
class="d-inline-block"
|
class="d-inline-block"
|
||||||
color="primary"
|
color="primary"
|
||||||
@ -42,15 +47,121 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row my-5">
|
<div *ngIf="hasPermissionForStatistics" class="row mb-5">
|
||||||
|
<div
|
||||||
|
class="col-md-4 d-flex my-1"
|
||||||
|
[ngClass]="{ 'justify-content-center': this.deviceType !== 'mobile' }"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="d-block"
|
||||||
|
title="Ghostfolio in Numbers: Monthly Active Users (MAU)"
|
||||||
|
[routerLink]="['/about']"
|
||||||
|
>
|
||||||
|
<gf-value
|
||||||
|
icon="people-outline"
|
||||||
|
size="large"
|
||||||
|
[value]="statistics?.activeUsers30d ?? '-'"
|
||||||
|
>Monthly Active Users</gf-value
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="col-md-4 d-flex my-1"
|
||||||
|
[ngClass]="{ 'justify-content-center': this.deviceType !== 'mobile' }"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="d-block"
|
||||||
|
title="Ghostfolio in Numbers: Stars on GitHub"
|
||||||
|
[routerLink]="['/about']"
|
||||||
|
>
|
||||||
|
<gf-value
|
||||||
|
icon="star-outline"
|
||||||
|
size="large"
|
||||||
|
[value]="statistics?.gitHubStargazers ?? '-'"
|
||||||
|
>Stars on GitHub</gf-value
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="col-md-4 d-flex my-1"
|
||||||
|
[ngClass]="{ 'justify-content-center': this.deviceType !== 'mobile' }"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="d-block"
|
||||||
|
title="Ghostfolio in Numbers: Pulls on Docker Hub"
|
||||||
|
[routerLink]="['/about']"
|
||||||
|
>
|
||||||
|
<gf-value
|
||||||
|
icon="cloud-download-outline"
|
||||||
|
size="large"
|
||||||
|
[value]="statistics?.dockerHubPulls ?? '-'"
|
||||||
|
>Pulls on Docker Hub</gf-value
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-5">
|
||||||
|
<div class="col-12 text-center text-muted"><small>As seen in</small></div>
|
||||||
|
<div class="col-md-2 d-flex justify-content-center my-1">
|
||||||
|
<a
|
||||||
|
class="d-block logo logo-alternative-to mask"
|
||||||
|
href="https://alternativeto.net"
|
||||||
|
target="_blank"
|
||||||
|
title="AlternativeTo - Crowdsourced software recommendations"
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 d-flex justify-content-center my-1">
|
||||||
|
<a
|
||||||
|
class="d-block logo logo-awesome"
|
||||||
|
href="https://github.com/awesome-selfhosted/awesome-selfhosted"
|
||||||
|
target="_blank"
|
||||||
|
title="Awesome-Selfhosted: A list of Free Software network services and web applications which can be hosted on your own servers"
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 d-flex justify-content-center my-1">
|
||||||
|
<a
|
||||||
|
class="d-block logo logo-openstartup"
|
||||||
|
href="https://openstartup.tm"
|
||||||
|
target="_blank"
|
||||||
|
title="Open Startup: The most complete list of open startups"
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 d-flex justify-content-center my-1">
|
||||||
|
<a
|
||||||
|
class="d-block logo logo-privacy-tools mask"
|
||||||
|
href="https://www.privacytools.io"
|
||||||
|
target="_blank"
|
||||||
|
title="Privacy Tools: Software Alternatives and Encryption"
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 d-flex justify-content-center my-1">
|
||||||
|
<a
|
||||||
|
class="d-block logo logo-product-hunt"
|
||||||
|
href="https://www.producthunt.com"
|
||||||
|
target="_blank"
|
||||||
|
title="Product Hunt – The best new products in tech."
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 d-flex justify-content-center my-1">
|
||||||
|
<a
|
||||||
|
class="d-block logo logo-unraid mask"
|
||||||
|
href="https://unraid.net"
|
||||||
|
target="_blank"
|
||||||
|
title="Unraid | Unleash Your Hardware"
|
||||||
|
></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-3 row">
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<h2 class="h4 mb-1 text-center">
|
<h2 class="h4 mb-1 text-center">
|
||||||
Protect your <strong>assets</strong>. Refine your
|
Protect your <strong>assets</strong>. Refine your
|
||||||
<strong>personal investment strategy</strong>.
|
<strong>personal investment strategy</strong>.
|
||||||
</h2>
|
</h2>
|
||||||
<p class="lead">
|
<p class="lead m-0">
|
||||||
Ghostfolio empowers busy people to keep track of stocks, ETFs or
|
Ghostfolio empowers busy people to keep track of stocks, ETFs or
|
||||||
cryptocurrencies and make solid, data-driven investment decisions.
|
cryptocurrencies without being tracked.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -58,20 +169,20 @@
|
|||||||
<div class="row my-3">
|
<div class="row my-3">
|
||||||
<div class="col-md-4 my-2">
|
<div class="col-md-4 my-2">
|
||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-title class="text-center">360° View</mat-card-title>
|
<mat-card-title>360° View</mat-card-title>
|
||||||
Get the full picture of your personal finances across multiple
|
Get the full picture of your personal finances across multiple
|
||||||
platforms.
|
platforms.
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 my-2">
|
<div class="col-md-4 my-2">
|
||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-title class="text-center">Web3 Ready</mat-card-title>
|
<mat-card-title>Web3 Ready</mat-card-title>
|
||||||
Use Ghostfolio anonymously and own your financial data.
|
Use Ghostfolio anonymously and own your financial data.
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 my-2">
|
<div class="col-md-4 my-2">
|
||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-title class="text-center">Open Source</mat-card-title>
|
<mat-card-title>Open Source</mat-card-title>
|
||||||
Benefit from continuous improvements through a strong community.
|
Benefit from continuous improvements through a strong community.
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
@ -123,7 +234,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="mt-4 text-center">
|
<div class="mt-4 text-center">
|
||||||
<a [routerLink]="['/about']" mat-stroked-button
|
<a mat-stroked-button [routerLink]="['/about']"
|
||||||
>Learn more about Ghostfolio</a
|
>Learn more about Ghostfolio</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@ -196,7 +307,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row my-5">
|
<div class="row my-5">
|
||||||
<div class="col-md-6 offset-md-3">
|
<div class="col">
|
||||||
<h2 class="h4 mb-1 text-center">Are <strong>you</strong> ready?</h2>
|
<h2 class="h4 mb-1 text-center">Are <strong>you</strong> ready?</h2>
|
||||||
<p class="lead mb-3 text-center">
|
<p class="lead mb-3 text-center">
|
||||||
Join now or check out the example account
|
Join now or check out the example account
|
||||||
@ -240,7 +351,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="align-items-center d-flex flex-column w-100">
|
<div class="align-items-center d-flex flex-column w-100">
|
||||||
<a
|
<a
|
||||||
class="agplv3-logo"
|
class="logo logo-agplv3 mask"
|
||||||
href="https://www.gnu.org/licenses/agpl-3.0.html"
|
href="https://www.gnu.org/licenses/agpl-3.0.html"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
title="GNU Affero General Public License Version 3"
|
title="GNU Affero General Public License Version 3"
|
||||||
|
@ -4,6 +4,7 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { GfLogoModule } from '@ghostfolio/ui/logo';
|
import { GfLogoModule } from '@ghostfolio/ui/logo';
|
||||||
|
import { GfValueModule } from '@ghostfolio/ui/value';
|
||||||
|
|
||||||
import { LandingPageRoutingModule } from './landing-page-routing.module';
|
import { LandingPageRoutingModule } from './landing-page-routing.module';
|
||||||
import { LandingPageComponent } from './landing-page.component';
|
import { LandingPageComponent } from './landing-page.component';
|
||||||
@ -13,6 +14,7 @@ import { LandingPageComponent } from './landing-page.component';
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfLogoModule,
|
GfLogoModule,
|
||||||
|
GfValueModule,
|
||||||
LandingPageRoutingModule,
|
LandingPageRoutingModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
|
@ -3,16 +3,6 @@
|
|||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
.agplv3-logo {
|
|
||||||
background-color: rgba(var(--dark-primary-text));
|
|
||||||
height: 3rem;
|
|
||||||
mask-image: url('/assets/images/AGPLv3-logo.svg');
|
|
||||||
mask-position: center;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-size: contain;
|
|
||||||
width: 7.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-container {
|
.button-container {
|
||||||
.mat-stroked-button {
|
.mat-stroked-button {
|
||||||
background-color: var(--light-background);
|
background-color: var(--light-background);
|
||||||
@ -34,6 +24,58 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 3rem;
|
||||||
|
width: 7.5rem;
|
||||||
|
|
||||||
|
&.mask {
|
||||||
|
background-color: rgba(var(--dark-secondary-text));
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.logo-agplv3 {
|
||||||
|
mask-image: url('/assets/images/logo-AGPLv3.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
&.logo-alternative-to {
|
||||||
|
mask-image: url('/assets/images/logo-alternative-to.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
&.logo-awesome {
|
||||||
|
background-image: url('/assets/images/logo-awesome.png');
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
filter: grayscale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.logo-openstartup {
|
||||||
|
background-image: url('/assets/images/logo-openstartup.png');
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
filter: grayscale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.logo-privacy-tools {
|
||||||
|
mask-image: url('/assets/images/logo-privacy-tools.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
&.logo-product-hunt {
|
||||||
|
background-image: url('/assets/images/logo-product-hunt.png');
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
filter: grayscale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.logo-unraid {
|
||||||
|
mask-image: url('/assets/images/logo-unraid.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.outro-inner-container {
|
.outro-inner-container {
|
||||||
aspect-ratio: 16 / 9;
|
aspect-ratio: 16 / 9;
|
||||||
max-height: 66vh;
|
max-height: 66vh;
|
||||||
@ -56,16 +98,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host-context(.is-dark-theme) {
|
:host-context(.is-dark-theme) {
|
||||||
.agplv3-logo {
|
|
||||||
background-color: rgba(var(--light-primary-text));
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-container {
|
.button-container {
|
||||||
.mat-stroked-button {
|
.mat-stroked-button {
|
||||||
background-color: var(--dark-background);
|
background-color: var(--dark-background);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
&.logo-agplv3,
|
||||||
|
&.logo-alternative-to,
|
||||||
|
&.logo-privacy-tools,
|
||||||
|
&.logo-unraid {
|
||||||
|
background-color: rgba(var(--light-primary-text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.outro-inner-container {
|
.outro-inner-container {
|
||||||
div {
|
div {
|
||||||
background-image: url('/assets/intro-dark.jpg') !important;
|
background-image: url('/assets/intro-dark.jpg') !important;
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { Market, ToggleOption } from '@ghostfolio/common/types';
|
import { Market, ToggleOption } from '@ghostfolio/common/types';
|
||||||
|
import { translate } from '@ghostfolio/ui/i18n';
|
||||||
import { Account, AssetClass, DataSource } from '@prisma/client';
|
import { Account, AssetClass, DataSource } from '@prisma/client';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject, Subscription } from 'rxjs';
|
import { Subject, Subscription } from 'rxjs';
|
||||||
@ -174,7 +175,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
for (const assetClass of Object.keys(AssetClass)) {
|
for (const assetClass of Object.keys(AssetClass)) {
|
||||||
assetClassFilters.push({
|
assetClassFilters.push({
|
||||||
id: assetClass,
|
id: assetClass,
|
||||||
label: assetClass,
|
label: translate(assetClass),
|
||||||
type: 'ASSET_CLASS'
|
type: 'ASSET_CLASS'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -450,6 +451,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
dataSource,
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
baseCurrency: this.user?.settings?.baseCurrency,
|
baseCurrency: this.user?.settings?.baseCurrency,
|
||||||
|
colorScheme: this.user?.settings?.colorScheme,
|
||||||
deviceType: this.deviceType,
|
deviceType: this.deviceType,
|
||||||
hasImpersonationId: this.hasImpersonationId,
|
hasImpersonationId: this.hasImpersonationId,
|
||||||
hasPermissionToReportDataGlitch: hasPermission(
|
hasPermissionToReportDataGlitch: hasPermission(
|
||||||
|
@ -13,12 +13,12 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<mat-card class="mb-3">
|
<mat-card class="mb-3">
|
||||||
<mat-card-header class="overflow-hidden w-100">
|
<mat-card-header class="mb-2 overflow-hidden w-100">
|
||||||
<mat-card-title class="text-truncate" i18n
|
<mat-card-title class="m-0 text-truncate" i18n
|
||||||
>Proportion of Net Worth</mat-card-title
|
>Proportion of Net Worth</mat-card-title
|
||||||
>
|
>
|
||||||
<gf-value
|
<gf-value
|
||||||
class="align-items-end flex-grow-1 ml-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"
|
||||||
@ -50,6 +50,7 @@
|
|||||||
<gf-portfolio-proportion-chart
|
<gf-portfolio-proportion-chart
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[colorScheme]="user?.settings?.colorScheme"
|
||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
[keys]="['id']"
|
[keys]="['id']"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
@ -79,6 +80,7 @@
|
|||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<gf-portfolio-proportion-chart
|
<gf-portfolio-proportion-chart
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[colorScheme]="user?.settings?.colorScheme"
|
||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
[keys]="['currency']"
|
[keys]="['currency']"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
@ -107,6 +109,7 @@
|
|||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<gf-portfolio-proportion-chart
|
<gf-portfolio-proportion-chart
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[colorScheme]="user?.settings?.colorScheme"
|
||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
[keys]="['assetClass', 'assetSubClass']"
|
[keys]="['assetClass', 'assetSubClass']"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
@ -133,6 +136,7 @@
|
|||||||
class="mx-auto"
|
class="mx-auto"
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[colorScheme]="user?.settings?.colorScheme"
|
||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
[keys]="['symbol']"
|
[keys]="['symbol']"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
@ -163,6 +167,7 @@
|
|||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<gf-portfolio-proportion-chart
|
<gf-portfolio-proportion-chart
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[colorScheme]="user?.settings?.colorScheme"
|
||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
[keys]="['name']"
|
[keys]="['name']"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
@ -192,6 +197,7 @@
|
|||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<gf-portfolio-proportion-chart
|
<gf-portfolio-proportion-chart
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[colorScheme]="user?.settings?.colorScheme"
|
||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
[keys]="['name']"
|
[keys]="['name']"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
@ -220,6 +226,7 @@
|
|||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<gf-portfolio-proportion-chart
|
<gf-portfolio-proportion-chart
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[colorScheme]="user?.settings?.colorScheme"
|
||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
[keys]="['name']"
|
[keys]="['name']"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.component';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
@ -26,6 +27,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
public benchmarkDataItems: HistoricalDataItem[] = [];
|
public benchmarkDataItems: HistoricalDataItem[] = [];
|
||||||
public benchmarks: Partial<SymbolProfile>[];
|
public benchmarks: Partial<SymbolProfile>[];
|
||||||
public bottom3: Position[];
|
public bottom3: Position[];
|
||||||
|
public dateRangeOptions = ToggleComponent.DEFAULT_DATE_RANGE_OPTIONS;
|
||||||
public daysInMarket: number;
|
public daysInMarket: number;
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public firstOrderDate: Date;
|
public firstOrderDate: Date;
|
||||||
@ -33,12 +35,13 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
public investments: InvestmentItem[];
|
public investments: InvestmentItem[];
|
||||||
public investmentsByMonth: InvestmentItem[];
|
public investmentsByMonth: InvestmentItem[];
|
||||||
public isLoadingBenchmarkComparator: boolean;
|
public isLoadingBenchmarkComparator: boolean;
|
||||||
public mode: GroupBy;
|
public isLoadingInvestmentChart: boolean;
|
||||||
|
public mode: GroupBy = 'month';
|
||||||
public modeOptions: ToggleOption[] = [
|
public modeOptions: ToggleOption[] = [
|
||||||
{ label: $localize`Monthly`, value: 'month' },
|
{ label: $localize`Monthly`, value: 'month' }
|
||||||
{ label: $localize`Accumulating`, value: undefined }
|
|
||||||
];
|
];
|
||||||
public performanceDataItems: HistoricalDataItem[];
|
public performanceDataItems: HistoricalDataItem[];
|
||||||
|
public performanceDataItemsInPercentage: HistoricalDataItem[];
|
||||||
public top3: Position[];
|
public top3: Position[];
|
||||||
public user: User;
|
public user: User;
|
||||||
|
|
||||||
@ -122,37 +125,51 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private update() {
|
private update() {
|
||||||
if (this.user.settings.isExperimentalFeatures) {
|
this.isLoadingBenchmarkComparator = true;
|
||||||
this.isLoadingBenchmarkComparator = true;
|
this.isLoadingInvestmentChart = true;
|
||||||
|
|
||||||
this.dataService
|
|
||||||
.fetchPortfolioPerformance({
|
|
||||||
range: this.user?.settings?.dateRange,
|
|
||||||
version: 2
|
|
||||||
})
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(({ chart }) => {
|
|
||||||
this.firstOrderDate = new Date(chart?.[0]?.date ?? new Date());
|
|
||||||
this.performanceDataItems = chart;
|
|
||||||
|
|
||||||
this.updateBenchmarkDataItems();
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchInvestments()
|
.fetchPortfolioPerformance({
|
||||||
|
range: this.user?.settings?.dateRange
|
||||||
|
})
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(({ firstOrderDate, investments }) => {
|
.subscribe(({ chart, firstOrderDate }) => {
|
||||||
|
this.firstOrderDate = firstOrderDate ?? new Date();
|
||||||
this.daysInMarket = differenceInDays(new Date(), firstOrderDate);
|
this.daysInMarket = differenceInDays(new Date(), firstOrderDate);
|
||||||
this.investments = investments;
|
|
||||||
|
this.investments = [];
|
||||||
|
this.performanceDataItems = [];
|
||||||
|
this.performanceDataItemsInPercentage = [];
|
||||||
|
|
||||||
|
for (const {
|
||||||
|
date,
|
||||||
|
netPerformanceInPercentage,
|
||||||
|
totalInvestment,
|
||||||
|
value
|
||||||
|
} of chart) {
|
||||||
|
this.investments.push({ date, investment: totalInvestment });
|
||||||
|
this.performanceDataItems.push({
|
||||||
|
date,
|
||||||
|
value
|
||||||
|
});
|
||||||
|
this.performanceDataItemsInPercentage.push({
|
||||||
|
date,
|
||||||
|
value: netPerformanceInPercentage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoadingInvestmentChart = false;
|
||||||
|
|
||||||
|
this.updateBenchmarkDataItems();
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchInvestmentsByMonth()
|
.fetchInvestments({
|
||||||
|
groupBy: 'month',
|
||||||
|
range: this.user?.settings?.dateRange
|
||||||
|
})
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(({ investments }) => {
|
.subscribe(({ investments }) => {
|
||||||
this.investmentsByMonth = investments;
|
this.investmentsByMonth = investments;
|
||||||
@ -161,7 +178,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchPositions({ range: 'max' })
|
.fetchPositions({ range: this.user?.settings?.dateRange })
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(({ positions }) => {
|
.subscribe(({ positions }) => {
|
||||||
const positionsSorted = sortBy(
|
const positionsSorted = sortBy(
|
||||||
@ -210,6 +227,8 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
this.benchmarkDataItems = [];
|
||||||
|
|
||||||
this.isLoadingBenchmarkComparator = false;
|
this.isLoadingBenchmarkComparator = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,27 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<h3 class="d-flex justify-content-center mb-3" i18n>Analysis</h3>
|
<h3 class="d-flex justify-content-center" i18n>Analysis</h3>
|
||||||
<div *ngIf="user?.settings?.isExperimentalFeatures" class="mb-5 row">
|
<div *ngIf="user?.settings?.viewMode !== 'ZEN'" class="my-4 text-center">
|
||||||
|
<gf-toggle
|
||||||
|
[defaultValue]="user?.settings?.dateRange"
|
||||||
|
[isLoading]="isLoadingBenchmarkComparator"
|
||||||
|
[options]="dateRangeOptions"
|
||||||
|
(change)="onChangeDateRange($event.value)"
|
||||||
|
></gf-toggle>
|
||||||
|
</div>
|
||||||
|
<div class="mb-5 row">
|
||||||
<div class="col-lg">
|
<div class="col-lg">
|
||||||
<gf-benchmark-comparator
|
<gf-benchmark-comparator
|
||||||
class="h-100"
|
class="h-100"
|
||||||
[benchmark]="user?.settings?.benchmark"
|
[benchmark]="user?.settings?.benchmark"
|
||||||
[benchmarkDataItems]="benchmarkDataItems"
|
[benchmarkDataItems]="benchmarkDataItems"
|
||||||
[benchmarks]="benchmarks"
|
[benchmarks]="benchmarks"
|
||||||
|
[colorScheme]="user?.settings?.colorScheme"
|
||||||
[daysInMarket]="daysInMarket"
|
[daysInMarket]="daysInMarket"
|
||||||
[isLoading]="isLoadingBenchmarkComparator"
|
[isLoading]="isLoadingBenchmarkComparator"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[performanceDataItems]="performanceDataItems"
|
[performanceDataItems]="performanceDataItemsInPercentage"
|
||||||
[user]="user"
|
[user]="user"
|
||||||
(benchmarkChanged)="onChangeBenchmark($event)"
|
(benchmarkChanged)="onChangeBenchmark($event)"
|
||||||
(dateRangeChanged)="onChangeDateRange($event)"
|
|
||||||
></gf-benchmark-comparator>
|
></gf-benchmark-comparator>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -96,6 +104,35 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-5 row">
|
||||||
|
<div class="col-lg">
|
||||||
|
<div class="align-items-center d-flex mb-4">
|
||||||
|
<div
|
||||||
|
class="align-items-center d-flex flex-grow-1 h5 mb-0 text-truncate"
|
||||||
|
>
|
||||||
|
<span i18n>Portfolio Evolution</span>
|
||||||
|
<gf-premium-indicator
|
||||||
|
*ngIf="user?.subscription?.type === 'Basic'"
|
||||||
|
class="ml-1"
|
||||||
|
></gf-premium-indicator>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-container">
|
||||||
|
<gf-investment-chart
|
||||||
|
class="h-100"
|
||||||
|
[benchmarkDataItems]="investments"
|
||||||
|
[currency]="user?.settings?.baseCurrency"
|
||||||
|
[daysInMarket]="daysInMarket"
|
||||||
|
[historicalDataItems]="performanceDataItems"
|
||||||
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
|
[isLoading]="isLoadingBenchmarkComparator"
|
||||||
|
[locale]="user?.settings?.locale"
|
||||||
|
[range]="user?.settings?.dateRange"
|
||||||
|
></gf-investment-chart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg">
|
<div class="col-lg">
|
||||||
<div class="align-items-center d-flex mb-4">
|
<div class="align-items-center d-flex mb-4">
|
||||||
@ -117,24 +154,15 @@
|
|||||||
></gf-toggle>
|
></gf-toggle>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart-container">
|
<div class="chart-container">
|
||||||
<gf-investment-chart
|
|
||||||
class="h-100"
|
|
||||||
[currency]="user?.settings?.baseCurrency"
|
|
||||||
[daysInMarket]="daysInMarket"
|
|
||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
|
||||||
[investments]="investments"
|
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
[ngClass]="{ 'd-none': mode }"
|
|
||||||
></gf-investment-chart>
|
|
||||||
<gf-investment-chart
|
<gf-investment-chart
|
||||||
class="h-100"
|
class="h-100"
|
||||||
groupBy="month"
|
groupBy="month"
|
||||||
|
[benchmarkDataItems]="investmentsByMonth"
|
||||||
[currency]="user?.settings?.baseCurrency"
|
[currency]="user?.settings?.baseCurrency"
|
||||||
[daysInMarket]="daysInMarket"
|
[daysInMarket]="daysInMarket"
|
||||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||||
[investments]="investmentsByMonth"
|
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[ngClass]="{ 'd-none': !mode }"
|
[range]="user?.settings?.dateRange"
|
||||||
[savingsRate]="(hasImpersonationId || user.settings.isRestrictedView) ? undefined : user?.settings?.savingsRate"
|
[savingsRate]="(hasImpersonationId || user.settings.isRestrictedView) ? undefined : user?.settings?.savingsRate"
|
||||||
></gf-investment-chart>
|
></gf-investment-chart>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<h4 class="mb-3" i18n>Calculator</h4>
|
<h4 class="mb-3" i18n>Calculator</h4>
|
||||||
<gf-fire-calculator
|
<gf-fire-calculator
|
||||||
|
[colorScheme]="user?.settings?.colorScheme"
|
||||||
[currency]="user?.settings?.baseCurrency"
|
[currency]="user?.settings?.baseCurrency"
|
||||||
[deviceType]="deviceType"
|
[deviceType]="deviceType"
|
||||||
[fireWealth]="fireWealth?.toNumber()"
|
[fireWealth]="fireWealth?.toNumber()"
|
||||||
@ -35,7 +36,7 @@
|
|||||||
}"
|
}"
|
||||||
></ngx-skeleton-loader>
|
></ngx-skeleton-loader>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!isLoading">
|
<div *ngIf="!isLoading" i18n>
|
||||||
If you retire today, you would be able to withdraw
|
If you retire today, you would be able to withdraw
|
||||||
<span class="font-weight-bold"
|
<span class="font-weight-bold"
|
||||||
><gf-value
|
><gf-value
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
|
import { translate } from '@ghostfolio/ui/i18n';
|
||||||
import { AssetClass, DataSource } from '@prisma/client';
|
import { AssetClass, DataSource } from '@prisma/client';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject, Subscription } from 'rxjs';
|
import { Subject, Subscription } from 'rxjs';
|
||||||
@ -130,7 +131,7 @@ export class HoldingsPageComponent implements OnDestroy, OnInit {
|
|||||||
for (const assetClass of Object.keys(AssetClass)) {
|
for (const assetClass of Object.keys(AssetClass)) {
|
||||||
assetClassFilters.push({
|
assetClassFilters.push({
|
||||||
id: assetClass,
|
id: assetClass,
|
||||||
label: assetClass,
|
label: translate(assetClass),
|
||||||
type: 'ASSET_CLASS'
|
type: 'ASSET_CLASS'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -192,6 +193,7 @@ export class HoldingsPageComponent implements OnDestroy, OnInit {
|
|||||||
dataSource,
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
baseCurrency: this.user?.settings?.baseCurrency,
|
baseCurrency: this.user?.settings?.baseCurrency,
|
||||||
|
colorScheme: this.user?.settings?.colorScheme,
|
||||||
deviceType: this.deviceType,
|
deviceType: this.deviceType,
|
||||||
hasImpersonationId: this.hasImpersonationId,
|
hasImpersonationId: this.hasImpersonationId,
|
||||||
hasPermissionToReportDataGlitch: hasPermission(
|
hasPermissionToReportDataGlitch: hasPermission(
|
||||||
|
@ -14,6 +14,7 @@ import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
|
|||||||
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
|
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
|
||||||
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
|
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
|
import { translate } from '@ghostfolio/ui/i18n';
|
||||||
import { AssetClass, AssetSubClass, Type } from '@prisma/client';
|
import { AssetClass, AssetSubClass, Type } from '@prisma/client';
|
||||||
import { isUUID } from 'class-validator';
|
import { isUUID } from 'class-validator';
|
||||||
import { isString } from 'lodash';
|
import { isString } from 'lodash';
|
||||||
@ -40,8 +41,12 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
|
|||||||
@ViewChild('autocomplete') autocomplete;
|
@ViewChild('autocomplete') autocomplete;
|
||||||
|
|
||||||
public activityForm: FormGroup;
|
public activityForm: FormGroup;
|
||||||
public assetClasses = Object.keys(AssetClass);
|
public assetClasses = Object.keys(AssetClass).map((assetClass) => {
|
||||||
public assetSubClasses = Object.keys(AssetSubClass);
|
return { id: assetClass, label: translate(assetClass) };
|
||||||
|
});
|
||||||
|
public assetSubClasses = Object.keys(AssetSubClass).map((assetSubClass) => {
|
||||||
|
return { id: assetSubClass, label: translate(assetSubClass) };
|
||||||
|
});
|
||||||
public currencies: string[] = [];
|
public currencies: string[] = [];
|
||||||
public currentMarketPrice = null;
|
public currentMarketPrice = null;
|
||||||
public filteredLookupItems: LookupItem[];
|
public filteredLookupItems: LookupItem[];
|
||||||
|
@ -4,17 +4,17 @@
|
|||||||
(keyup.enter)="activityForm.valid && onSubmit()"
|
(keyup.enter)="activityForm.valid && onSubmit()"
|
||||||
(ngSubmit)="onSubmit()"
|
(ngSubmit)="onSubmit()"
|
||||||
>
|
>
|
||||||
<h1 *ngIf="data.activity.id" mat-dialog-title i18n>Update activity</h1>
|
<h1 *ngIf="data.activity.id" i18n mat-dialog-title>Update activity</h1>
|
||||||
<h1 *ngIf="!data.activity.id" mat-dialog-title i18n>Add activity</h1>
|
<h1 *ngIf="!data.activity.id" i18n mat-dialog-title>Add activity</h1>
|
||||||
<div class="flex-grow-1" mat-dialog-content>
|
<div class="flex-grow-1" mat-dialog-content>
|
||||||
<div>
|
<div>
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Type</mat-label>
|
<mat-label i18n>Type</mat-label>
|
||||||
<mat-select formControlName="type">
|
<mat-select formControlName="type">
|
||||||
<mat-option value="BUY" i18n>BUY</mat-option>
|
<mat-option i18n value="BUY">BUY</mat-option>
|
||||||
<mat-option value="DIVIDEND" i18n>DIVIDEND</mat-option>
|
<mat-option i18n value="DIVIDEND">DIVIDEND</mat-option>
|
||||||
<mat-option value="ITEM" i18n>ITEM</mat-option>
|
<mat-option i18n value="ITEM">ITEM</mat-option>
|
||||||
<mat-option value="SELL" i18n>SELL</mat-option>
|
<mat-option i18n value="SELL">SELL</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
@ -156,8 +156,8 @@
|
|||||||
<mat-option [value]="null"></mat-option>
|
<mat-option [value]="null"></mat-option>
|
||||||
<mat-option
|
<mat-option
|
||||||
*ngFor="let assetClass of assetClasses"
|
*ngFor="let assetClass of assetClasses"
|
||||||
[value]="assetClass"
|
[value]="assetClass.id"
|
||||||
>{{ assetClass }}</mat-option
|
>{{ assetClass.label }}</mat-option
|
||||||
>
|
>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
@ -171,8 +171,8 @@
|
|||||||
<mat-option [value]="null"></mat-option>
|
<mat-option [value]="null"></mat-option>
|
||||||
<mat-option
|
<mat-option
|
||||||
*ngFor="let assetSubClass of assetSubClasses"
|
*ngFor="let assetSubClass of assetSubClasses"
|
||||||
[value]="assetSubClass"
|
[value]="assetSubClass.id"
|
||||||
>{{ assetSubClass }}</mat-option
|
>{{ assetSubClass.label }}</mat-option
|
||||||
>
|
>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
@ -188,7 +188,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
input.type = 'file';
|
input.type = 'file';
|
||||||
|
|
||||||
input.onchange = (event) => {
|
input.onchange = (event) => {
|
||||||
this.snackBar.open('⏳' + $localize`Importing data...`);
|
this.snackBar.open('⏳ ' + $localize`Importing data...`);
|
||||||
|
|
||||||
// Getting the file reference
|
// Getting the file reference
|
||||||
const file = (event.target as HTMLInputElement).files[0];
|
const file = (event.target as HTMLInputElement).files[0];
|
||||||
@ -334,9 +334,13 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
private handleImportSuccess() {
|
private handleImportSuccess() {
|
||||||
this.fetchActivities();
|
this.fetchActivities();
|
||||||
|
|
||||||
this.snackBar.open('✅' + $localize`Import has been completed`, undefined, {
|
this.snackBar.open(
|
||||||
duration: 3000
|
'✅ ' + $localize`Import has been completed`,
|
||||||
});
|
undefined,
|
||||||
|
{
|
||||||
|
duration: 3000
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private openCreateTransactionDialog(aActivity?: Activity): void {
|
private openCreateTransactionDialog(aActivity?: Activity): void {
|
||||||
@ -405,6 +409,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
dataSource,
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
baseCurrency: this.user?.settings?.baseCurrency,
|
baseCurrency: this.user?.settings?.baseCurrency,
|
||||||
|
colorScheme: this.user?.settings?.colorScheme,
|
||||||
deviceType: this.deviceType,
|
deviceType: this.deviceType,
|
||||||
hasImpersonationId: this.hasImpersonationId,
|
hasImpersonationId: this.hasImpersonationId,
|
||||||
hasPermissionToReportDataGlitch: hasPermission(
|
hasPermissionToReportDataGlitch: hasPermission(
|
||||||
|
@ -25,7 +25,6 @@ import {
|
|||||||
Filter,
|
Filter,
|
||||||
InfoItem,
|
InfoItem,
|
||||||
OAuthResponse,
|
OAuthResponse,
|
||||||
PortfolioChart,
|
|
||||||
PortfolioDetails,
|
PortfolioDetails,
|
||||||
PortfolioInvestments,
|
PortfolioInvestments,
|
||||||
PortfolioPerformanceResponse,
|
PortfolioPerformanceResponse,
|
||||||
@ -36,7 +35,13 @@ import {
|
|||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { filterGlobalPermissions } from '@ghostfolio/common/permissions';
|
import { filterGlobalPermissions } from '@ghostfolio/common/permissions';
|
||||||
import { AccountWithValue, DateRange } from '@ghostfolio/common/types';
|
import { AccountWithValue, DateRange } from '@ghostfolio/common/types';
|
||||||
import { DataSource, Order as OrderModel } from '@prisma/client';
|
import { translate } from '@ghostfolio/ui/i18n';
|
||||||
|
import {
|
||||||
|
AssetClass,
|
||||||
|
AssetSubClass,
|
||||||
|
DataSource,
|
||||||
|
Order as OrderModel
|
||||||
|
} from '@prisma/client';
|
||||||
import { format, parseISO } from 'date-fns';
|
import { format, parseISO } from 'date-fns';
|
||||||
import { cloneDeep, groupBy } from 'lodash';
|
import { cloneDeep, groupBy } from 'lodash';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
@ -74,60 +79,19 @@ export class DataService {
|
|||||||
}: {
|
}: {
|
||||||
filters?: Filter[];
|
filters?: Filter[];
|
||||||
}): Observable<Activities> {
|
}): Observable<Activities> {
|
||||||
let params = new HttpParams();
|
return this.http
|
||||||
|
.get<any>('/api/v1/order', {
|
||||||
if (filters?.length > 0) {
|
params: this.buildFiltersAsQueryParams({ filters })
|
||||||
const {
|
|
||||||
ACCOUNT: filtersByAccount,
|
|
||||||
ASSET_CLASS: filtersByAssetClass,
|
|
||||||
TAG: filtersByTag
|
|
||||||
} = groupBy(filters, (filter) => {
|
|
||||||
return filter.type;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (filtersByAccount) {
|
|
||||||
params = params.append(
|
|
||||||
'accounts',
|
|
||||||
filtersByAccount
|
|
||||||
.map(({ id }) => {
|
|
||||||
return id;
|
|
||||||
})
|
|
||||||
.join(',')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filtersByAssetClass) {
|
|
||||||
params = params.append(
|
|
||||||
'assetClasses',
|
|
||||||
filtersByAssetClass
|
|
||||||
.map(({ id }) => {
|
|
||||||
return id;
|
|
||||||
})
|
|
||||||
.join(',')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filtersByTag) {
|
|
||||||
params = params.append(
|
|
||||||
'tags',
|
|
||||||
filtersByTag
|
|
||||||
.map(({ id }) => {
|
|
||||||
return id;
|
|
||||||
})
|
|
||||||
.join(',')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.http.get<any>('/api/v1/order', { params }).pipe(
|
|
||||||
map(({ activities }) => {
|
|
||||||
for (const activity of activities) {
|
|
||||||
activity.createdAt = parseISO(activity.createdAt);
|
|
||||||
activity.date = parseISO(activity.date);
|
|
||||||
}
|
|
||||||
return { activities };
|
|
||||||
})
|
})
|
||||||
);
|
.pipe(
|
||||||
|
map(({ activities }) => {
|
||||||
|
for (const activity of activities) {
|
||||||
|
activity.createdAt = parseISO(activity.createdAt);
|
||||||
|
activity.date = parseISO(activity.date);
|
||||||
|
}
|
||||||
|
return { activities };
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchAdminData() {
|
public fetchAdminData() {
|
||||||
@ -135,30 +99,8 @@ export class DataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fetchAdminMarketData({ filters }: { filters?: Filter[] }) {
|
public fetchAdminMarketData({ filters }: { filters?: Filter[] }) {
|
||||||
let params = new HttpParams();
|
|
||||||
|
|
||||||
if (filters?.length > 0) {
|
|
||||||
const { ASSET_SUB_CLASS: filtersByAssetSubClass } = groupBy(
|
|
||||||
filters,
|
|
||||||
(filter) => {
|
|
||||||
return filter.type;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (filtersByAssetSubClass) {
|
|
||||||
params = params.append(
|
|
||||||
'assetSubClasses',
|
|
||||||
filtersByAssetSubClass
|
|
||||||
.map(({ id }) => {
|
|
||||||
return id;
|
|
||||||
})
|
|
||||||
.join(',')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.http.get<AdminMarketData>('/api/v1/admin/market-data', {
|
return this.http.get<AdminMarketData>('/api/v1/admin/market-data', {
|
||||||
params
|
params: this.buildFiltersAsQueryParams({ filters })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,12 +143,6 @@ export class DataService {
|
|||||||
return this.http.get<BenchmarkResponse>('/api/v1/benchmark');
|
return this.http.get<BenchmarkResponse>('/api/v1/benchmark');
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchChart({ range, version }: { range: DateRange; version: number }) {
|
|
||||||
return this.http.get<PortfolioChart>(`/api/v${version}/portfolio/chart`, {
|
|
||||||
params: { range }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public fetchExport(activityIds?: string[]) {
|
public fetchExport(activityIds?: string[]) {
|
||||||
let params = new HttpParams();
|
let params = new HttpParams();
|
||||||
|
|
||||||
@ -233,34 +169,19 @@ export class DataService {
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchInvestments(): Observable<PortfolioInvestments> {
|
public fetchInvestments({
|
||||||
return this.http.get<any>('/api/v1/portfolio/investments').pipe(
|
groupBy,
|
||||||
map((response) => {
|
range
|
||||||
if (response.firstOrderDate) {
|
}: {
|
||||||
response.firstOrderDate = parseISO(response.firstOrderDate);
|
groupBy?: 'month';
|
||||||
}
|
range: DateRange;
|
||||||
|
}) {
|
||||||
return response;
|
return this.http.get<PortfolioInvestments>(
|
||||||
})
|
'/api/v1/portfolio/investments',
|
||||||
|
{ params: { groupBy, range } }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchInvestmentsByMonth(): Observable<PortfolioInvestments> {
|
|
||||||
return this.http
|
|
||||||
.get<any>('/api/v1/portfolio/investments', {
|
|
||||||
params: { groupBy: 'month' }
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
map((response) => {
|
|
||||||
if (response.firstOrderDate) {
|
|
||||||
response.firstOrderDate = parseISO(response.firstOrderDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public fetchSymbolItem({
|
public fetchSymbolItem({
|
||||||
dataSource,
|
dataSource,
|
||||||
includeHistoricalData,
|
includeHistoricalData,
|
||||||
@ -306,54 +227,9 @@ export class DataService {
|
|||||||
}: {
|
}: {
|
||||||
filters?: Filter[];
|
filters?: Filter[];
|
||||||
}): Observable<PortfolioDetails> {
|
}): Observable<PortfolioDetails> {
|
||||||
let params = new HttpParams();
|
|
||||||
|
|
||||||
if (filters?.length > 0) {
|
|
||||||
const {
|
|
||||||
ACCOUNT: filtersByAccount,
|
|
||||||
ASSET_CLASS: filtersByAssetClass,
|
|
||||||
TAG: filtersByTag
|
|
||||||
} = groupBy(filters, (filter) => {
|
|
||||||
return filter.type;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (filtersByAccount) {
|
|
||||||
params = params.append(
|
|
||||||
'accounts',
|
|
||||||
filtersByAccount
|
|
||||||
.map(({ id }) => {
|
|
||||||
return id;
|
|
||||||
})
|
|
||||||
.join(',')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filtersByAssetClass) {
|
|
||||||
params = params.append(
|
|
||||||
'assetClasses',
|
|
||||||
filtersByAssetClass
|
|
||||||
.map(({ id }) => {
|
|
||||||
return id;
|
|
||||||
})
|
|
||||||
.join(',')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filtersByTag) {
|
|
||||||
params = params.append(
|
|
||||||
'tags',
|
|
||||||
filtersByTag
|
|
||||||
.map(({ id }) => {
|
|
||||||
return id;
|
|
||||||
})
|
|
||||||
.join(',')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.http
|
return this.http
|
||||||
.get<any>('/api/v1/portfolio/details', {
|
.get<any>('/api/v1/portfolio/details', {
|
||||||
params
|
params: this.buildFiltersAsQueryParams({ filters })
|
||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
map((response) => {
|
map((response) => {
|
||||||
@ -362,22 +238,40 @@ export class DataService {
|
|||||||
response.summary.firstOrderDate
|
response.summary.firstOrderDate
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response.holdings) {
|
||||||
|
for (const symbol of Object.keys(response.holdings)) {
|
||||||
|
response.holdings[symbol].assetClass = translate(
|
||||||
|
response.holdings[symbol].assetClass
|
||||||
|
);
|
||||||
|
|
||||||
|
response.holdings[symbol].assetSubClass = translate(
|
||||||
|
response.holdings[symbol].assetSubClass
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchPortfolioPerformance({
|
public fetchPortfolioPerformance({
|
||||||
range,
|
range
|
||||||
version
|
|
||||||
}: {
|
}: {
|
||||||
range: DateRange;
|
range: DateRange;
|
||||||
version: number;
|
}): Observable<PortfolioPerformanceResponse> {
|
||||||
}) {
|
return this.http
|
||||||
return this.http.get<PortfolioPerformanceResponse>(
|
.get<any>(`/api/v2/portfolio/performance`, { params: { range } })
|
||||||
`/api/v${version}/portfolio/performance`,
|
.pipe(
|
||||||
{ params: { range } }
|
map((response) => {
|
||||||
);
|
if (response.firstOrderDate) {
|
||||||
|
response.firstOrderDate = parseISO(response.firstOrderDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchPortfolioPublic(aId: string) {
|
public fetchPortfolioPublic(aId: string) {
|
||||||
@ -410,6 +304,20 @@ export class DataService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.SymbolProfile) {
|
||||||
|
if (data.SymbolProfile.assetClass) {
|
||||||
|
data.SymbolProfile.assetClass = <AssetClass>(
|
||||||
|
translate(data.SymbolProfile.assetClass)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.SymbolProfile.assetSubClass) {
|
||||||
|
data.SymbolProfile.assetSubClass = <AssetSubClass>(
|
||||||
|
translate(data.SymbolProfile.assetSubClass)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -458,4 +366,53 @@ export class DataService {
|
|||||||
couponCode
|
couponCode
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildFiltersAsQueryParams({ filters }: { filters?: Filter[] }) {
|
||||||
|
let params = new HttpParams();
|
||||||
|
|
||||||
|
if (filters?.length > 0) {
|
||||||
|
const {
|
||||||
|
ACCOUNT: filtersByAccount,
|
||||||
|
ASSET_CLASS: filtersByAssetClass,
|
||||||
|
TAG: filtersByTag
|
||||||
|
} = groupBy(filters, (filter) => {
|
||||||
|
return filter.type;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filtersByAccount) {
|
||||||
|
params = params.append(
|
||||||
|
'accounts',
|
||||||
|
filtersByAccount
|
||||||
|
.map(({ id }) => {
|
||||||
|
return id;
|
||||||
|
})
|
||||||
|
.join(',')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filtersByAssetClass) {
|
||||||
|
params = params.append(
|
||||||
|
'assetClasses',
|
||||||
|
filtersByAssetClass
|
||||||
|
.map(({ id }) => {
|
||||||
|
return id;
|
||||||
|
})
|
||||||
|
.join(',')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filtersByTag) {
|
||||||
|
params = params.append(
|
||||||
|
'tags',
|
||||||
|
filtersByTag
|
||||||
|
.map(({ id }) => {
|
||||||
|
return id;
|
||||||
|
})
|
||||||
|
.join(',')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http';
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
|
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
|
||||||
import { Account, DataSource, Type } from '@prisma/client';
|
import { Account, DataSource, Type } from '@prisma/client';
|
||||||
import { parse } from 'date-fns';
|
import { isMatch, parse, parseISO } from 'date-fns';
|
||||||
import { isFinite } from 'lodash';
|
import { isFinite } from 'lodash';
|
||||||
import { parse as csvToJson } from 'papaparse';
|
import { parse as csvToJson } from 'papaparse';
|
||||||
import { EMPTY } from 'rxjs';
|
import { EMPTY } from 'rxjs';
|
||||||
@ -153,13 +153,15 @@ export class ImportTransactionsService {
|
|||||||
|
|
||||||
for (const key of ImportTransactionsService.DATE_KEYS) {
|
for (const key of ImportTransactionsService.DATE_KEYS) {
|
||||||
if (item[key]) {
|
if (item[key]) {
|
||||||
try {
|
if (isMatch(item[key], 'dd-MM-yyyy')) {
|
||||||
date = parse(item[key], 'dd-MM-yyyy', new Date()).toISOString();
|
date = parse(item[key], 'dd-MM-yyyy', new Date()).toISOString();
|
||||||
} catch {}
|
} else if (isMatch(item[key], 'dd/MM/yyyy')) {
|
||||||
|
|
||||||
try {
|
|
||||||
date = parse(item[key], 'dd/MM/yyyy', new Date()).toISOString();
|
date = parse(item[key], 'dd/MM/yyyy', new Date()).toISOString();
|
||||||
} catch {}
|
} else {
|
||||||
|
try {
|
||||||
|
date = parseISO(item[key]).toISOString();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
if (date) {
|
if (date) {
|
||||||
return date;
|
return date;
|
||||||
|
BIN
apps/client/src/assets/images/blog/hacktoberfest-2022.png
Normal file
After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
1
apps/client/src/assets/images/logo-alternative-to.svg
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
apps/client/src/assets/images/logo-awesome.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
apps/client/src/assets/images/logo-openstartup.png
Normal file
After Width: | Height: | Size: 56 KiB |
35
apps/client/src/assets/images/logo-privacy-tools.svg
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<svg version="1.2" baseProfile="tiny-ps" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 508 101" width="508" height="101">
|
||||||
|
<title>horizontal-svg</title>
|
||||||
|
<style>
|
||||||
|
tspan { white-space:pre }
|
||||||
|
.shp0 { fill: #3498db }
|
||||||
|
.shp1 { fill: transparent }
|
||||||
|
.shp2 { fill: #8a9595 }
|
||||||
|
</style>
|
||||||
|
<g id="Logo">
|
||||||
|
<g id="Shield">
|
||||||
|
<g id="Layer">
|
||||||
|
<path id="Layer" class="shp0" d="M284.81 0C279.86 0.24 254.33 8.51 242.56 14.89C240.87 15.81 239.01 18.96 239 20.34C239 33 242.05 51.67 250.66 68.26L267.5 51.42C264.25 43.19 266.19 33.81 272.44 27.56C276.25 23.94 283.41 21.26 289.64 21.15C292.47 21.1 295.11 21.58 297.15 22.74C298.34 23.43 297.78 24.32 297.42 24.68L284.52 37.05C282.89 38.8 284.3 43.29 285.36 46.15C288.21 47.2 292.7 48.61 294.45 46.98L306.83 34.08C307.18 33.73 308.07 33.17 308.76 34.36C312.47 40.88 309.21 53.53 303.95 59.06C297.69 65.32 288.31 67.26 280.09 64L260.76 83.33C267.04 90.61 274.95 96.69 284.81 100.54C320.46 86.63 330.62 43.46 330.62 20.34C330.61 18.96 328.76 15.81 327.06 14.89C315.29 8.51 289.76 0.24 284.81 0L284.81 0Z" />
|
||||||
|
<g id="Layer">
|
||||||
|
<path id="Layer" class="shp1" d="M250.69 68.23C250.93 68.68 251.17 69.13 251.41 69.58C252.43 71.46 253.52 73.31 254.69 75.12C255.86 76.93 257.1 78.69 258.43 80.4C259.19 81.38 259.98 82.35 260.8 83.3L280.12 63.97C288.35 67.23 297.73 65.29 303.98 59.03C309.25 53.5 312.51 40.85 308.8 34.33C308.11 33.14 307.22 33.7 306.86 34.05L294.49 46.95C292.74 48.58 288.25 47.17 285.39 46.12C284.34 43.26 282.93 38.77 284.56 37.02L297.46 24.65C297.81 24.29 298.37 23.4 297.18 22.71C295.14 21.55 292.51 21.07 289.68 21.12C283.45 21.23 276.28 23.91 272.48 27.53C266.22 33.79 264.28 43.16 267.54 51.39L250.69 68.23Z" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="Tools">
|
||||||
|
<path id="Layer" class="shp2" d="M343.3 31.55L343.3 24.61L387.83 24.61L387.83 31.55L369.48 31.55L369.48 76.83L361.65 76.83L361.65 31.55L343.3 31.55Z" />
|
||||||
|
<path id="Layer" fill-rule="evenodd" class="shp2" d="M381.33 50.12C382.38 47.68 383.79 45.59 385.58 43.85C387.37 42.06 389.46 40.67 391.85 39.67C394.29 38.63 396.87 38.11 399.61 38.11C402.34 38.11 404.9 38.63 407.29 39.67C409.73 40.67 411.82 42.06 413.56 43.85C415.35 45.59 416.74 47.68 417.73 50.12C418.78 52.5 419.3 55.06 419.3 57.8C419.3 60.58 418.78 63.2 417.73 65.63C416.74 68.02 415.35 70.11 413.56 71.9C411.82 73.64 409.73 75.03 407.29 76.08C404.9 77.07 402.34 77.57 399.61 77.57C396.87 77.57 394.29 77.07 391.85 76.08C389.46 75.03 387.37 73.64 385.58 71.9C383.79 70.11 382.38 68.02 381.33 65.63C380.34 63.2 379.84 60.58 379.84 57.8C379.84 55.06 380.34 52.5 381.33 50.12ZM408.33 67.2C410.77 64.61 411.99 61.48 411.99 57.8C411.99 54.12 410.77 51.01 408.33 48.48C405.95 45.94 403.04 44.67 399.61 44.67C396.18 44.67 393.24 45.94 390.8 48.48C388.37 51.01 387.15 54.12 387.15 57.8C387.15 61.48 388.34 64.61 390.73 67.2C393.17 69.73 396.13 71 399.61 71C403.04 71 405.95 69.73 408.33 67.2Z" />
|
||||||
|
<path id="Layer" fill-rule="evenodd" class="shp2" d="M423.45 50.12C424.5 47.68 425.91 45.59 427.7 43.85C429.49 42.06 431.58 40.67 433.97 39.67C436.41 38.63 438.99 38.11 441.73 38.11C444.46 38.11 447.02 38.63 449.41 39.67C451.85 40.67 453.94 42.06 455.68 43.85C457.47 45.59 458.86 47.68 459.85 50.12C460.9 52.5 461.42 55.06 461.42 57.8C461.42 60.58 460.9 63.2 459.85 65.63C458.86 68.02 457.47 70.11 455.68 71.9C453.94 73.64 451.85 75.03 449.41 76.08C447.02 77.07 444.46 77.57 441.73 77.57C438.99 77.57 436.41 77.07 433.97 76.08C431.58 75.03 429.49 73.64 427.7 71.9C425.91 70.11 424.5 68.02 423.45 65.63C422.46 63.2 421.96 60.58 421.96 57.8C421.96 55.06 422.46 52.5 423.45 50.12ZM450.45 67.2C452.89 64.61 454.11 61.48 454.11 57.8C454.11 54.12 452.89 51.01 450.45 48.48C448.07 45.94 445.16 44.67 441.73 44.67C438.3 44.67 435.36 45.94 432.92 48.48C430.49 51.01 429.27 54.12 429.27 57.8C429.27 61.48 430.46 64.61 432.85 67.2C435.29 69.73 438.25 71 441.73 71C445.16 71 448.07 69.73 450.45 67.2Z" />
|
||||||
|
<path id="Layer" class="shp2" d="M473.1 22.97L473.1 76.83L465.64 76.83L465.64 24.61L473.1 22.97Z" />
|
||||||
|
<path id="Layer" class="shp2" d="M475.48 71.9L479.51 66.98C481.5 68.52 483.51 69.71 485.55 70.56C487.64 71.35 489.75 71.75 491.89 71.75C494.58 71.75 496.76 71.23 498.45 70.18C500.15 69.14 500.99 67.77 500.99 66.08C500.99 64.74 500.49 63.67 499.5 62.87C498.5 62.08 496.96 61.53 494.87 61.23L488.01 60.26C484.28 59.72 481.45 58.57 479.51 56.83C477.62 55.04 476.67 52.68 476.67 49.75C476.67 46.31 478.04 43.55 480.78 41.47C483.51 39.33 487.09 38.26 491.52 38.26C494.3 38.26 496.94 38.66 499.42 39.45C501.96 40.25 504.42 41.47 506.81 43.11L503 48.03C500.92 46.64 498.88 45.64 496.89 45.05C494.95 44.4 492.98 44.08 490.99 44.08C488.71 44.08 486.87 44.55 485.48 45.49C484.08 46.44 483.39 47.68 483.39 49.22C483.39 50.62 483.86 51.69 484.8 52.43C485.8 53.13 487.41 53.65 489.65 54L496.51 54.97C500.24 55.51 503.08 56.68 505.02 58.47C507.01 60.26 508 62.63 508 65.56C508 67.25 507.58 68.84 506.73 70.33C505.94 71.78 504.82 73.02 503.38 74.06C501.99 75.11 500.32 75.95 498.38 76.6C496.44 77.2 494.35 77.49 492.11 77.49C488.83 77.49 485.75 77.02 482.86 76.08C480.03 75.13 477.57 73.74 475.48 71.9L475.48 71.9Z" />
|
||||||
|
</g>
|
||||||
|
<g id="Privacy">
|
||||||
|
<path id="Layer" fill-rule="evenodd" class="shp2" d="M1.46 76.82L1.46 24.6L25.63 24.6C30.7 24.6 34.8 26.07 37.94 29.01C41.12 31.89 42.71 35.64 42.71 40.27C42.71 44.85 41.12 48.57 37.94 51.46C34.75 54.34 30.65 55.79 25.63 55.79L9.29 55.79L9.29 76.82L1.46 76.82ZM24.81 31.47L9.29 31.47L9.29 49.15L24.81 49.15C27.84 49.15 30.25 48.35 32.04 46.76C33.88 45.12 34.8 42.95 34.8 40.27C34.8 37.58 33.88 35.45 32.04 33.85C30.25 32.26 27.84 31.47 24.81 31.47L24.81 31.47Z" />
|
||||||
|
<path id="Layer" class="shp2" d="M46.91 76.82L46.91 38.85L54.37 38.85L54.37 43.7C55.51 41.86 56.98 40.47 58.77 39.52C60.56 38.53 62.57 38.03 64.81 38.03C65.61 38.03 66.3 38.08 66.9 38.18C67.5 38.28 68.07 38.43 68.62 38.63L68.62 45.34C67.92 45.09 67.2 44.89 66.45 44.74C65.71 44.6 64.96 44.52 64.22 44.52C62.03 44.52 60.06 45.12 58.32 46.31C56.63 47.46 55.31 49.17 54.37 51.46L54.37 76.82L46.91 76.82Z" />
|
||||||
|
<path id="Layer" class="shp2" d="M75.41 32.74C74.17 32.74 73.1 32.29 72.2 31.4C71.31 30.45 70.86 29.36 70.86 28.12C70.86 26.87 71.31 25.8 72.2 24.91C73.1 23.96 74.17 23.49 75.41 23.49C76.65 23.49 77.72 23.96 78.62 24.91C79.56 25.8 80.03 26.87 80.03 28.12C80.03 29.36 79.56 30.45 78.62 31.4C77.72 32.29 76.65 32.74 75.41 32.74L75.41 32.74ZM79.14 38.86L79.14 76.82L71.68 76.82L71.68 38.86L79.14 38.86Z" />
|
||||||
|
<path id="Layer" class="shp2" d="M97.33 76.82L80.92 38.85L89.12 38.85L100.98 67.27L112.84 38.85L120.83 38.85L104.41 76.82L97.33 76.82Z" />
|
||||||
|
<path id="Layer" fill-rule="evenodd" class="shp2" d="M134.56 77.5C130.43 77.5 127.08 76.43 124.49 74.29C121.9 72.15 120.61 69.37 120.61 65.94C120.61 62.36 121.98 59.55 124.71 57.51C127.45 55.42 131.2 54.38 135.98 54.38C137.82 54.38 139.61 54.57 141.35 54.97C143.09 55.32 144.75 55.82 146.35 56.46L146.35 52.44C146.35 49.75 145.55 47.74 143.96 46.39C142.37 45.05 140.08 44.38 137.1 44.38C135.36 44.38 133.54 44.65 131.65 45.2C129.76 45.7 127.65 46.49 125.31 47.59L122.55 41.99C125.39 40.65 128.07 39.68 130.61 39.08C133.14 38.44 135.65 38.11 138.14 38.11C143.06 38.11 146.87 39.31 149.55 41.7C152.29 44.03 153.66 47.36 153.66 51.69L153.66 76.83L146.35 76.83L146.35 73.55C144.66 74.89 142.84 75.88 140.9 76.53C138.96 77.18 136.85 77.5 134.56 77.5L134.56 77.5ZM127.77 65.79C127.77 67.63 128.54 69.12 130.08 70.26C131.68 71.41 133.74 71.98 136.28 71.98C138.26 71.98 140.1 71.68 141.8 71.09C143.49 70.49 145 69.59 146.35 68.4L146.35 61.84C144.9 61.04 143.39 60.47 141.8 60.12C140.2 59.72 138.46 59.52 136.57 59.52C133.89 59.52 131.75 60.09 130.16 61.24C128.57 62.38 127.77 63.9 127.77 65.79L127.77 65.79Z" />
|
||||||
|
<path id="Layer" class="shp2" d="M177.4 70.93C179.29 70.93 181.08 70.56 182.77 69.81C184.46 69.02 186.08 67.85 187.62 66.31L192.09 71.15C190.11 73.14 187.82 74.71 185.23 75.85C182.65 76.95 179.94 77.49 177.1 77.49C174.37 77.49 171.81 77 169.42 76C167.03 74.96 164.97 73.57 163.23 71.83C161.49 70.08 160.09 68.02 159.05 65.63C158.06 63.2 157.56 60.59 157.56 57.8C157.56 55.07 158.06 52.51 159.05 50.12C160.09 47.68 161.49 45.59 163.23 43.85C164.97 42.06 167.03 40.67 169.42 39.68C171.81 38.63 174.37 38.11 177.1 38.11C179.94 38.11 182.7 38.68 185.38 39.82C188.07 40.92 190.4 42.46 192.39 44.45L187.69 49.52C186.25 47.98 184.64 46.81 182.85 46.02C181.06 45.17 179.19 44.75 177.25 44.75C173.82 44.75 170.91 46.02 168.52 48.55C166.14 51.09 164.94 54.17 164.94 57.8C164.94 61.53 166.14 64.66 168.52 67.2C170.96 69.69 173.92 70.93 177.4 70.93L177.4 70.93Z" />
|
||||||
|
<path id="Layer" class="shp2" d="M204.75 80.4L206.24 76.97L191.1 38.85L199.31 38.85L210.42 67.87L222.65 38.85L230.71 38.85L212.36 81.37C210.62 85.4 208.63 88.28 206.39 90.02C204.15 91.81 201.29 92.71 197.81 92.71C197.02 92.71 196.27 92.66 195.57 92.56C194.93 92.51 194.36 92.41 193.86 92.26L193.86 85.7C194.36 85.8 194.85 85.87 195.35 85.92C195.9 85.97 196.52 85.99 197.22 85.99C198.91 85.99 200.37 85.52 201.62 84.58C202.91 83.68 203.95 82.29 204.75 80.4L204.75 80.4Z" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 8.9 KiB |
BIN
apps/client/src/assets/images/logo-product-hunt.png
Normal file
After Width: | Height: | Size: 21 KiB |
9
apps/client/src/assets/images/logo-unraid.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 222.4 139.8">
|
||||||
|
<path fill="#ffffff" d="M146.70000000000002 130.2H135l-3 9h-6.5l13.4-38.5h8l13.4 38.5h-7.1l-10.6-31.6-5.8 16.9h8.2l1.7 5.7zM29.7 100.8v25.4c0 8.9-5.8 13.6-14.9 13.6-9 0-14.8-4.7-14.8-13.6v-25.4h6.5v25.4c0 5.2 3.2 7.9 8.2 7.9 5.2 0 8.4-2.7 8.4-7.9v-25.4h6.6zM50.9 112.7v26.5h-6.5v-38.5h6.1l17 26.5v-26.5H74v38.5h-6.1l-17-26.5zM171.3 100.8h6.5v38.5h-6.5v-38.5zM222.4 125.4c0 9-5.9 13.8-15.2 13.8h-14.5v-38.5h14.6c9.2 0 15.1 4.8 15.1 13.8v10.9zm-6.6-10.8c0-5.3-3.3-8.1-8.5-8.1h-8.1v27.1h8c5.3 0 8.6-2.8 8.6-8.1v-10.9zM108.3 124.7c4.3-1.6 6.9-5.3 6.9-11.5 0-8.7-5.1-12.4-12.8-12.4H88.8v38.5h6.5v-32.8h6.9c3.8 0 6.2 1.8 6.2 6.7s-2.4 6.8-6.2 6.8h-3.4l9.2 19.4h7.5l-7.2-14.7z" class="st0"></path>
|
||||||
|
<linearGradient id="SVGID_1_" x1="68.07" x2="154.073" y1="81.489" y2="-4.514" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#e32929"></stop>
|
||||||
|
<stop offset="1" stop-color="#ff8d30"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
<path fill="url(#SVGID_1_)" d="M107.8 19.2h6.5v38.5h-6.5V19.2zM50.9 57.7h-6.5V19.2h6.5v38.5zm25.2 4.6h6.5V77h-6.5V62.3zM60.2 45.8h6.5v23.8h-6.5V45.8zm31.7 0h6.5v23.8h-6.5V45.8zm79.4-26.6h6.5v38.5h-6.5V19.2zm-25.2-4.5h-6.5V0h6.5v14.7zM162 31.1h-6.5V7.4h6.5v23.7zm-31.8 0h-6.5V7.4h6.5v23.7z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
@ -6,70 +6,74 @@
|
|||||||
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io</loc>
|
<loc>https://ghostfol.io</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/de/blog/2021/07/hallo-ghostfolio</loc>
|
<loc>https://ghostfol.io/de/blog/2021/07/hallo-ghostfolio</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/about</loc>
|
<loc>https://ghostfol.io/en/about</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/about/changelog</loc>
|
<loc>https://ghostfol.io/en/about/changelog</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog</loc>
|
<loc>https://ghostfol.io/en/blog</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2021/07/hello-ghostfolio</loc>
|
<loc>https://ghostfol.io/en/blog/2021/07/hello-ghostfolio</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2022/01/ghostfolio-first-months-in-open-source</loc>
|
<loc>https://ghostfol.io/en/blog/2022/01/ghostfolio-first-months-in-open-source</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2022/07/ghostfolio-meets-internet-identity</loc>
|
<loc>https://ghostfol.io/en/blog/2022/07/ghostfolio-meets-internet-identity</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2022/07/how-do-i-get-my-finances-in-order</loc>
|
<loc>https://ghostfol.io/en/blog/2022/07/how-do-i-get-my-finances-in-order</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/blog/2022/08/500-stars-on-github</loc>
|
<loc>https://ghostfol.io/en/blog/2022/08/500-stars-on-github</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://ghostfol.io/en/blog/2022/10/hacktoberfest-2022</loc>
|
||||||
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/demo</loc>
|
<loc>https://ghostfol.io/en/demo</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/faq</loc>
|
<loc>https://ghostfol.io/en/faq</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/features</loc>
|
<loc>https://ghostfol.io/en/features</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/markets</loc>
|
<loc>https://ghostfol.io/en/markets</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/pricing</loc>
|
<loc>https://ghostfol.io/en/pricing</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/register</loc>
|
<loc>https://ghostfol.io/en/register</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://ghostfol.io/en/resources</loc>
|
<loc>https://ghostfol.io/en/resources</loc>
|
||||||
<lastmod>2022-09-01T00:00:00+00:00</lastmod>
|
<lastmod>2022-10-16T00:00:00+00:00</lastmod>
|
||||||
</url>
|
</url>
|
||||||
</urlset>
|
</urlset>
|
||||||
|
@ -6,65 +6,65 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta content="yes" name="apple-mobile-web-app-capable" />
|
<meta content="yes" name="apple-mobile-web-app-capable" />
|
||||||
<meta
|
<meta
|
||||||
|
content="Ghostfolio is a personal finance dashboard to keep track of your assets like stocks, ETFs or cryptocurrencies across multiple platforms."
|
||||||
name="description"
|
name="description"
|
||||||
content="Ghostfolio is a lightweight application to keep track of your financial assets like stocks, ETFs or cryptocurrencies across multiple platforms."
|
|
||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
|
content="app, asset, cryptocurrency, dashboard, etf, finance, management, performance, portfolio, software, stock, trading, wealth, web3"
|
||||||
name="keywords"
|
name="keywords"
|
||||||
content="app, asset, cryptocurrency, etf, finance, management, performance, portfolio, software, stock, tracker, trading, wealth"
|
|
||||||
/>
|
/>
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta content="yes" name="mobile-web-app-capable" />
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta content="summary_large_image" name="twitter:card" />
|
||||||
<meta
|
<meta
|
||||||
|
content="Ghostfolio is a personal finance dashboard to keep track of your assets like stocks, ETFs or cryptocurrencies"
|
||||||
name="twitter:description"
|
name="twitter:description"
|
||||||
content="Ghostfolio is a lightweight wealth management application for individuals to keep track of stocks, ETFs or cryptocurrencies"
|
|
||||||
/>
|
/>
|
||||||
<meta name="twitter:image" content="${rootUrl}/${featureGraphicPath}" />
|
<meta content="${rootUrl}/${featureGraphicPath}" name="twitter:image" />
|
||||||
<meta
|
<meta
|
||||||
|
content="Ghostfolio – Open Source Wealth Management Software"
|
||||||
name="twitter:title"
|
name="twitter:title"
|
||||||
content="Ghostfolio – Open Source Wealth Management Software"
|
|
||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
|
||||||
content="initial-scale=1, viewport-fit=cover, width=device-width"
|
content="initial-scale=1, viewport-fit=cover, width=device-width"
|
||||||
|
name="viewport"
|
||||||
/>
|
/>
|
||||||
<meta property="og:description" content="" />
|
<meta content="" property="og:description" />
|
||||||
<meta
|
<meta
|
||||||
|
content="Ghostfolio – Open Source Wealth Management Software"
|
||||||
property="og:title"
|
property="og:title"
|
||||||
content="Ghostfolio – Open Source Wealth Management Software"
|
|
||||||
/>
|
/>
|
||||||
<meta property="og:type" content="website" />
|
<meta content="website" property="og:type" />
|
||||||
<meta property="og:url" content="${rootUrl}${path}" />
|
<meta content="${rootUrl}${path}" property="og:url" />
|
||||||
<meta property="og:image" content="${rootUrl}/${featureGraphicPath}" />
|
<meta content="${rootUrl}/${featureGraphicPath}" property="og:image" />
|
||||||
<meta property="og:updated_time" content="2022-08-18T00:00:00+00:00" />
|
<meta content="2022-10-16T00:00:00+00:00" property="og:updated_time" />
|
||||||
<meta
|
<meta
|
||||||
property="og:site_name"
|
|
||||||
content="Ghostfolio – Open Source Wealth Management Software"
|
content="Ghostfolio – Open Source Wealth Management Software"
|
||||||
|
property="og:site_name"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<link
|
<link
|
||||||
|
href="../assets/apple-touch-icon.png"
|
||||||
rel="apple-touch-icon"
|
rel="apple-touch-icon"
|
||||||
sizes="180x180"
|
sizes="180x180"
|
||||||
href="../assets/apple-touch-icon.png"
|
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
sizes="32x32"
|
|
||||||
href="../assets/favicon-32x32.png"
|
href="../assets/favicon-32x32.png"
|
||||||
|
rel="icon"
|
||||||
|
sizes="32x32"
|
||||||
|
type="image/png"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="icon"
|
|
||||||
type="image/png"
|
|
||||||
sizes="16x16"
|
|
||||||
href="../assets/favicon-16x16.png"
|
href="../assets/favicon-16x16.png"
|
||||||
|
rel="icon"
|
||||||
|
sizes="16x16"
|
||||||
|
type="image/png"
|
||||||
/>
|
/>
|
||||||
<link rel="manifest" href="../assets/site.webmanifest" />
|
<link href="../assets/site.webmanifest" rel="manifest" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<gf-root></gf-root>
|
<gf-root></gf-root>
|
||||||
|
|
||||||
<script type="module" src="../ionicons/ionicons.esm.js"></script>
|
<script src="../ionicons/ionicons.esm.js" type="module"></script>
|
||||||
<script nomodule="" src="ionicons.js"></script>
|
<script nomodule="" src="ionicons.js"></script>
|
||||||
|
|
||||||
<noscript
|
<noscript
|
||||||
|
@ -98,7 +98,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">214</context>
|
<context context-type="linenumber">218</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/portfolio-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/portfolio-page.html</context>
|
||||||
@ -354,7 +354,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">75</context>
|
<context context-type="linenumber">77</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="d7b35c384aecd25a516200d6921836374613dfe7" datatype="html">
|
<trans-unit id="d7b35c384aecd25a516200d6921836374613dfe7" datatype="html">
|
||||||
@ -418,7 +418,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">11</context>
|
<context context-type="linenumber">17</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5eebcc824c2d34b17abba8c9ef718c5ce118a5c8" datatype="html">
|
<trans-unit id="5eebcc824c2d34b17abba8c9ef718c5ce118a5c8" datatype="html">
|
||||||
@ -474,7 +474,7 @@
|
|||||||
<target state="translated">Stel uw systeemboodschap in:</target>
|
<target state="translated">Stel uw systeemboodschap in:</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.component.ts</context>
|
||||||
<context context-type="linenumber">199</context>
|
<context context-type="linenumber">202</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ec03f5c28b327fc7ecfc4b20a0a7cf14a75843ff" datatype="html">
|
<trans-unit id="ec03f5c28b327fc7ecfc4b20a0a7cf14a75843ff" datatype="html">
|
||||||
@ -490,7 +490,7 @@
|
|||||||
<target state="translated">per gebruiker</target>
|
<target state="translated">per gebruiker</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">15</context>
|
<context context-type="linenumber">26</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5067ede95804e0e0e2b078747848367a7373cc9b" datatype="html">
|
<trans-unit id="5067ede95804e0e0e2b078747848367a7373cc9b" datatype="html">
|
||||||
@ -498,7 +498,7 @@
|
|||||||
<target state="translated">Verzamel recente gegevens</target>
|
<target state="translated">Verzamel recente gegevens</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">33</context>
|
<context context-type="linenumber">44</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="27e27f3f7338baefacfd5668bad03ece9c5955df" datatype="html">
|
<trans-unit id="27e27f3f7338baefacfd5668bad03ece9c5955df" datatype="html">
|
||||||
@ -506,7 +506,7 @@
|
|||||||
<target state="translated">Alle gegevens verzamelen</target>
|
<target state="translated">Alle gegevens verzamelen</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">46</context>
|
<context context-type="linenumber">57</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="cc65b67b46b69cf06ff1f16a909e61612c9d57b8" datatype="html">
|
<trans-unit id="cc65b67b46b69cf06ff1f16a909e61612c9d57b8" datatype="html">
|
||||||
@ -518,7 +518,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">60</context>
|
<context context-type="linenumber">71</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="37ff0697aa81b2777250c483d2ce1ebdaa5ab042" datatype="html">
|
<trans-unit id="37ff0697aa81b2777250c483d2ce1ebdaa5ab042" datatype="html">
|
||||||
@ -526,7 +526,7 @@
|
|||||||
<target state="translated">Wisselkoersen</target>
|
<target state="translated">Wisselkoersen</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">67</context>
|
<context context-type="linenumber">78</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4567a660a7f67e254bbf13219906843e34d9f807" datatype="html">
|
<trans-unit id="4567a660a7f67e254bbf13219906843e34d9f807" datatype="html">
|
||||||
@ -534,7 +534,7 @@
|
|||||||
<target state="translated">Valuta toevoegen</target>
|
<target state="translated">Valuta toevoegen</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">106</context>
|
<context context-type="linenumber">117</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="860e5f056b59410ec8db65cb53955505c6931752" datatype="html">
|
<trans-unit id="860e5f056b59410ec8db65cb53955505c6931752" datatype="html">
|
||||||
@ -542,7 +542,7 @@
|
|||||||
<target state="translated">Systeembericht</target>
|
<target state="translated">Systeembericht</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">112</context>
|
<context context-type="linenumber">133</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="657028d5fc9c3da8f2d667b6b15cd0df8b9a3729" datatype="html">
|
<trans-unit id="657028d5fc9c3da8f2d667b6b15cd0df8b9a3729" datatype="html">
|
||||||
@ -550,7 +550,7 @@
|
|||||||
<target state="translated">Bericht instellen</target>
|
<target state="translated">Bericht instellen</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">134</context>
|
<context context-type="linenumber">155</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7fd64c34428887e4cd56d05534b89c100b8544ad" datatype="html">
|
<trans-unit id="7fd64c34428887e4cd56d05534b89c100b8544ad" datatype="html">
|
||||||
@ -558,7 +558,7 @@
|
|||||||
<target state="translated">Alleen lezen</target>
|
<target state="translated">Alleen lezen</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">139</context>
|
<context context-type="linenumber">160</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="e698b03c34b459b1b006d7f0473a49b9fcf5dfc1" datatype="html">
|
<trans-unit id="e698b03c34b459b1b006d7f0473a49b9fcf5dfc1" datatype="html">
|
||||||
@ -566,7 +566,7 @@
|
|||||||
<target state="translated">Coupons</target>
|
<target state="translated">Coupons</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">152</context>
|
<context context-type="linenumber">173</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="f6755cff4957d5c3c89bafce5651f1b6fa2b1fd9" datatype="html">
|
<trans-unit id="f6755cff4957d5c3c89bafce5651f1b6fa2b1fd9" datatype="html">
|
||||||
@ -574,7 +574,7 @@
|
|||||||
<target state="translated">Toevoegen</target>
|
<target state="translated">Toevoegen</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">187</context>
|
<context context-type="linenumber">208</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="e799e6b926557f0098f41888cdf8df868eff3d47" datatype="html">
|
<trans-unit id="e799e6b926557f0098f41888cdf8df868eff3d47" datatype="html">
|
||||||
@ -582,7 +582,7 @@
|
|||||||
<target state="translated">Huishouding</target>
|
<target state="translated">Huishouding</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">194</context>
|
<context context-type="linenumber">215</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="c7ac907e52a7ce2ac70b1786eb5f403ce306ce1f" datatype="html">
|
<trans-unit id="c7ac907e52a7ce2ac70b1786eb5f403ce306ce1f" datatype="html">
|
||||||
@ -590,7 +590,7 @@
|
|||||||
<target state="translated">Cache legen</target>
|
<target state="translated">Cache legen</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">198</context>
|
<context context-type="linenumber">219</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2817099043823177227" datatype="html">
|
<trans-unit id="2817099043823177227" datatype="html">
|
||||||
@ -942,11 +942,11 @@
|
|||||||
<target state="translated">Belegging</target>
|
<target state="translated">Belegging</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">40</context>
|
<context context-type="linenumber">37</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">117</context>
|
<context context-type="linenumber">119</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a64cd8d0131a583e3de081c9e6458af7aecdafbc" datatype="html">
|
<trans-unit id="a64cd8d0131a583e3de081c9e6458af7aecdafbc" datatype="html">
|
||||||
@ -954,7 +954,7 @@
|
|||||||
<target state="translated">Absoluut bruto rendement</target>
|
<target state="translated">Absoluut bruto rendement</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">51</context>
|
<context context-type="linenumber">48</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="e98a980fc12d6479b9c61431b905ce0c8ddc3ec5" datatype="html">
|
<trans-unit id="e98a980fc12d6479b9c61431b905ce0c8ddc3ec5" datatype="html">
|
||||||
@ -962,7 +962,7 @@
|
|||||||
<target state="translated">Bruto resultaat (TWR)</target>
|
<target state="translated">Bruto resultaat (TWR)</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">62</context>
|
<context context-type="linenumber">59</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="e5c369abfbe1bd70f577aa03b2679797e38d7590" datatype="html">
|
<trans-unit id="e5c369abfbe1bd70f577aa03b2679797e38d7590" datatype="html">
|
||||||
@ -970,7 +970,7 @@
|
|||||||
<target state="translated">Transactiekosten voor <x id="INTERPOLATION" equiv-text="{{ summary?.ordersCount }}"/> <x id="ICU" equiv-text="{summary?.ordersCount, plural, =1 {transaction} other {transactions}}"/> </target>
|
<target state="translated">Transactiekosten voor <x id="INTERPOLATION" equiv-text="{{ summary?.ordersCount }}"/> <x id="ICU" equiv-text="{summary?.ordersCount, plural, =1 {transaction} other {transactions}}"/> </target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">77,80</context>
|
<context context-type="linenumber">74,77</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="272c7fd98af55bfa5b9d579176f1cfa25cd5489f" datatype="html">
|
<trans-unit id="272c7fd98af55bfa5b9d579176f1cfa25cd5489f" datatype="html">
|
||||||
@ -978,7 +978,7 @@
|
|||||||
<target state="translated">{VAR_PLURAL, meervoud, =1 {transaction} other {transactions}}</target>
|
<target state="translated">{VAR_PLURAL, meervoud, =1 {transaction} other {transactions}}</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">78,79</context>
|
<context context-type="linenumber">75,76</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="de518fa0cd3a454e84102c0d9984c4a3cfdb590d" datatype="html">
|
<trans-unit id="de518fa0cd3a454e84102c0d9984c4a3cfdb590d" datatype="html">
|
||||||
@ -986,7 +986,7 @@
|
|||||||
<target state="translated">Absolute Netto Prestatie</target>
|
<target state="translated">Absolute Netto Prestatie</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">95</context>
|
<context context-type="linenumber">92</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ba987f3e4d4458aa36c61c03bedb60d98ad20393" datatype="html">
|
<trans-unit id="ba987f3e4d4458aa36c61c03bedb60d98ad20393" datatype="html">
|
||||||
@ -994,7 +994,7 @@
|
|||||||
<target state="translated">Netto resultaat (TWR)</target>
|
<target state="translated">Netto resultaat (TWR)</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">106</context>
|
<context context-type="linenumber">103</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="f159ffb7a09c8c1fe7913ef063462db50ddcd878" datatype="html">
|
<trans-unit id="f159ffb7a09c8c1fe7913ef063462db50ddcd878" datatype="html">
|
||||||
@ -1002,7 +1002,7 @@
|
|||||||
<target state="translated">Totaal Activa</target>
|
<target state="translated">Totaal Activa</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">122</context>
|
<context context-type="linenumber">119</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="59d87bdb983dda90c1e86d086430552ec05c7a4d" datatype="html">
|
<trans-unit id="59d87bdb983dda90c1e86d086430552ec05c7a4d" datatype="html">
|
||||||
@ -1010,7 +1010,7 @@
|
|||||||
<target state="translated">Kostbaarheden</target>
|
<target state="translated">Kostbaarheden</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">134</context>
|
<context context-type="linenumber">131</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2201b2004bc4997a66f6f8ea2511a4e1311f3de1" datatype="html">
|
<trans-unit id="2201b2004bc4997a66f6f8ea2511a4e1311f3de1" datatype="html">
|
||||||
@ -1018,7 +1018,7 @@
|
|||||||
<target state="translated">Noodfonds</target>
|
<target state="translated">Noodfonds</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">145</context>
|
<context context-type="linenumber">142</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8cce9d03787606e0052d19c2ae7e7fa5ff785e94" datatype="html">
|
<trans-unit id="8cce9d03787606e0052d19c2ae7e7fa5ff785e94" datatype="html">
|
||||||
@ -1026,7 +1026,7 @@
|
|||||||
<target state="translated">Koopkracht</target>
|
<target state="translated">Koopkracht</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">165</context>
|
<context context-type="linenumber">162</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="67251f04518ae452230c68a748b3fa2838b4db74" datatype="html">
|
<trans-unit id="67251f04518ae452230c68a748b3fa2838b4db74" datatype="html">
|
||||||
@ -1034,7 +1034,7 @@
|
|||||||
<target state="translated">Netto Waarde</target>
|
<target state="translated">Netto Waarde</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">190</context>
|
<context context-type="linenumber">187</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="e1b20ce1622d86ae0a75a3555a1a9ae7c2c60e58" datatype="html">
|
<trans-unit id="e1b20ce1622d86ae0a75a3555a1a9ae7c2c60e58" datatype="html">
|
||||||
@ -1042,7 +1042,7 @@
|
|||||||
<target state="translated">Jaarlijks rendement</target>
|
<target state="translated">Jaarlijks rendement</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">201</context>
|
<context context-type="linenumber">198</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="d3aa83bd247983dd056a62f56ffb25269bd98d47" datatype="html">
|
<trans-unit id="d3aa83bd247983dd056a62f56ffb25269bd98d47" datatype="html">
|
||||||
@ -1050,7 +1050,7 @@
|
|||||||
<target state="translated">Dividend</target>
|
<target state="translated">Dividend</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">217</context>
|
<context context-type="linenumber">214</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6785405835169448749" datatype="html">
|
<trans-unit id="6785405835169448749" datatype="html">
|
||||||
@ -1066,7 +1066,7 @@
|
|||||||
<target state="translated">Sectoren</target>
|
<target state="translated">Sectoren</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">187</context>
|
<context context-type="linenumber">189</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
||||||
@ -1078,7 +1078,7 @@
|
|||||||
<target state="translated">Landen</target>
|
<target state="translated">Landen</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">198</context>
|
<context context-type="linenumber">201</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="cafc87479686947e2590b9f588a88040aeaf660b" datatype="html">
|
<trans-unit id="cafc87479686947e2590b9f588a88040aeaf660b" datatype="html">
|
||||||
@ -1086,7 +1086,7 @@
|
|||||||
<target state="translated">Tags</target>
|
<target state="translated">Tags</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">234</context>
|
<context context-type="linenumber">238</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
||||||
@ -1098,7 +1098,7 @@
|
|||||||
<target state="translated">Gegevensstoring melden</target>
|
<target state="translated">Gegevensstoring melden</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">249</context>
|
<context context-type="linenumber">253</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
|
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
|
||||||
@ -1112,9 +1112,13 @@
|
|||||||
<trans-unit id="3cc9c2ae277393b3946b38c088dabff671b1ee1b" datatype="html">
|
<trans-unit id="3cc9c2ae277393b3946b38c088dabff671b1ee1b" datatype="html">
|
||||||
<source>Performance</source>
|
<source>Performance</source>
|
||||||
<target state="translated">Prestaties</target>
|
<target state="translated">Prestaties</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context>
|
||||||
|
<context context-type="linenumber">4</context>
|
||||||
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">55</context>
|
<context context-type="linenumber">57</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/positions-table/positions-table.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/positions-table/positions-table.component.html</context>
|
||||||
@ -1270,7 +1274,7 @@
|
|||||||
<target state="translated">Voer uw couponcode in:</target>
|
<target state="translated">Voer uw couponcode in:</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
||||||
<context context-type="linenumber">225</context>
|
<context context-type="linenumber">226</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4420880039966769543" datatype="html">
|
<trans-unit id="4420880039966769543" datatype="html">
|
||||||
@ -1278,7 +1282,7 @@
|
|||||||
<target state="translated">Kon kortingscode niet inwisselen</target>
|
<target state="translated">Kon kortingscode niet inwisselen</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
||||||
<context context-type="linenumber">235</context>
|
<context context-type="linenumber">236</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4819099731531004979" datatype="html">
|
<trans-unit id="4819099731531004979" datatype="html">
|
||||||
@ -1286,7 +1290,7 @@
|
|||||||
<target state="translated">Couponcode is ingewisseld</target>
|
<target state="translated">Couponcode is ingewisseld</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
||||||
<context context-type="linenumber">247</context>
|
<context context-type="linenumber">248</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7967484035994732534" datatype="html">
|
<trans-unit id="7967484035994732534" datatype="html">
|
||||||
@ -1294,7 +1298,7 @@
|
|||||||
<target state="translated">Herladen</target>
|
<target state="translated">Herladen</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
||||||
<context context-type="linenumber">248</context>
|
<context context-type="linenumber">249</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7963559562180316948" datatype="html">
|
<trans-unit id="7963559562180316948" datatype="html">
|
||||||
@ -1302,7 +1306,7 @@
|
|||||||
<target state="translated">Wilt u deze aanmeldingsmethode echt verwijderen?</target>
|
<target state="translated">Wilt u deze aanmeldingsmethode echt verwijderen?</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
||||||
<context context-type="linenumber">294</context>
|
<context context-type="linenumber">295</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="29881a45dafbe5aa05cd9d0441a4c0c2fb06df92" datatype="html">
|
<trans-unit id="29881a45dafbe5aa05cd9d0441a4c0c2fb06df92" datatype="html">
|
||||||
@ -1414,7 +1418,7 @@
|
|||||||
<target state="translated">Aanmelden met vingerafdruk</target>
|
<target state="translated">Aanmelden met vingerafdruk</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
<context context-type="linenumber">196</context>
|
<context context-type="linenumber">220</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="83c4d4d764d2e2725ab8e919ec16ac400e1f290a" datatype="html">
|
<trans-unit id="83c4d4d764d2e2725ab8e919ec16ac400e1f290a" datatype="html">
|
||||||
@ -1422,7 +1426,7 @@
|
|||||||
<target state="translated">Gebruikers-ID</target>
|
<target state="translated">Gebruikers-ID</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
<context context-type="linenumber">223</context>
|
<context context-type="linenumber">247</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="9021c579c084e68d9db06a569d76f024111c6c54" datatype="html">
|
<trans-unit id="9021c579c084e68d9db06a569d76f024111c6c54" datatype="html">
|
||||||
@ -1430,7 +1434,7 @@
|
|||||||
<target state="translated">Verleende toegang</target>
|
<target state="translated">Verleende toegang</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
<context context-type="linenumber">232</context>
|
<context context-type="linenumber">256</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5e41f1b4c46ad9e0a9bc83fa36445483aa5cc324" datatype="html">
|
<trans-unit id="5e41f1b4c46ad9e0a9bc83fa36445483aa5cc324" datatype="html">
|
||||||
@ -1638,7 +1642,7 @@
|
|||||||
<target state="translated">Per valuta</target>
|
<target state="translated">Per valuta</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">66</context>
|
<context context-type="linenumber">67</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8288ff761f2d259625d2e5a3d96db727926d9cd4" datatype="html">
|
<trans-unit id="8288ff761f2d259625d2e5a3d96db727926d9cd4" datatype="html">
|
||||||
@ -1646,7 +1650,7 @@
|
|||||||
<target state="translated">Per activaklasse</target>
|
<target state="translated">Per activaklasse</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">94</context>
|
<context context-type="linenumber">96</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="b64539bb7815eb3275b55ad723d3897fc6ba8d23" datatype="html">
|
<trans-unit id="b64539bb7815eb3275b55ad723d3897fc6ba8d23" datatype="html">
|
||||||
@ -1654,7 +1658,7 @@
|
|||||||
<target state="translated">Per participatie</target>
|
<target state="translated">Per participatie</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">122</context>
|
<context context-type="linenumber">125</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="9f86714c9a6b74e13c96ab02102ce40c34fe13b9" datatype="html">
|
<trans-unit id="9f86714c9a6b74e13c96ab02102ce40c34fe13b9" datatype="html">
|
||||||
@ -1662,7 +1666,7 @@
|
|||||||
<target state="translated">Per Sector</target>
|
<target state="translated">Per Sector</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">150</context>
|
<context context-type="linenumber">154</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7017e0e26b53ef322c3e3bbf95f06a85487a12b2" datatype="html">
|
<trans-unit id="7017e0e26b53ef322c3e3bbf95f06a85487a12b2" datatype="html">
|
||||||
@ -1670,7 +1674,7 @@
|
|||||||
<target state="translated">Per continent</target>
|
<target state="translated">Per continent</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">179</context>
|
<context context-type="linenumber">184</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="f27e9dd8de80176286e02312e694cb8d1e485a5d" datatype="html">
|
<trans-unit id="f27e9dd8de80176286e02312e694cb8d1e485a5d" datatype="html">
|
||||||
@ -1678,7 +1682,7 @@
|
|||||||
<target state="translated">Per land</target>
|
<target state="translated">Per land</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">207</context>
|
<context context-type="linenumber">213</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="85780db87ac6c9f202615ac63754551c061e7236" datatype="html">
|
<trans-unit id="85780db87ac6c9f202615ac63754551c061e7236" datatype="html">
|
||||||
@ -1686,7 +1690,7 @@
|
|||||||
<target state="translated">Regio's</target>
|
<target state="translated">Regio's</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">238</context>
|
<context context-type="linenumber">245</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
||||||
@ -1718,7 +1722,7 @@
|
|||||||
<target state="translated">Tijdlijn investeringen</target>
|
<target state="translated">Tijdlijn investeringen</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
|
||||||
<context context-type="linenumber">105</context>
|
<context context-type="linenumber">142</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html">
|
<trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html">
|
||||||
@ -1726,7 +1730,7 @@
|
|||||||
<target state="translated">Top</target>
|
<target state="translated">Top</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
|
||||||
<context context-type="linenumber">26</context>
|
<context context-type="linenumber">34</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
|
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
|
||||||
@ -1734,7 +1738,7 @@
|
|||||||
<target state="translated">Onder</target>
|
<target state="translated">Onder</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
|
||||||
<context context-type="linenumber">62</context>
|
<context context-type="linenumber">70</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5857197365507636437" datatype="html">
|
<trans-unit id="5857197365507636437" datatype="html">
|
||||||
@ -1770,7 +1774,7 @@
|
|||||||
<target state="translated">4% regel</target>
|
<target state="translated">4% regel</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/fire/fire-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/fire/fire-page.html</context>
|
||||||
<context context-type="linenumber">20</context>
|
<context context-type="linenumber">21</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="803941175683258052" datatype="html">
|
<trans-unit id="803941175683258052" datatype="html">
|
||||||
@ -1954,7 +1958,7 @@
|
|||||||
<target state="translated">Hoeveelheid</target>
|
<target state="translated">Hoeveelheid</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">107</context>
|
<context context-type="linenumber">109</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
||||||
@ -2006,7 +2010,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">145</context>
|
<context context-type="linenumber">147</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
||||||
@ -2034,7 +2038,7 @@
|
|||||||
<target state="translated">Import is voltooid</target>
|
<target state="translated">Import is voltooid</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/transactions-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/transactions-page.component.ts</context>
|
||||||
<context context-type="linenumber">337</context>
|
<context context-type="linenumber">338,337</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5080775557941296581" datatype="html">
|
<trans-unit id="5080775557941296581" datatype="html">
|
||||||
@ -2050,7 +2054,7 @@
|
|||||||
<target state="translated">Portefeuille</target>
|
<target state="translated">Portefeuille</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context>
|
||||||
<context context-type="linenumber">107</context>
|
<context context-type="linenumber">101</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page-routing.module.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page-routing.module.ts</context>
|
||||||
@ -2284,10 +2288,6 @@
|
|||||||
<trans-unit id="313fcf0f8dac5ff5800a3e6bd67cb1955089ccca" datatype="html">
|
<trans-unit id="313fcf0f8dac5ff5800a3e6bd67cb1955089ccca" datatype="html">
|
||||||
<source>Beta</source>
|
<source>Beta</source>
|
||||||
<target state="translated">Beta</target>
|
<target state="translated">Beta</target>
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context>
|
|
||||||
<context context-type="linenumber">5</context>
|
|
||||||
</context-group>
|
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
<context context-type="linenumber">119</context>
|
<context context-type="linenumber">119</context>
|
||||||
@ -2314,7 +2314,7 @@
|
|||||||
<target state="translated">Gegevensbeheer</target>
|
<target state="translated">Gegevensbeheer</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">20</context>
|
<context context-type="linenumber">31</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3ccabfc3dc288eaa2355ba43298c739f85951ec3" datatype="html">
|
<trans-unit id="3ccabfc3dc288eaa2355ba43298c739f85951ec3" datatype="html">
|
||||||
@ -2358,7 +2358,7 @@
|
|||||||
<target state="translated">Verandering</target>
|
<target state="translated">Verandering</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">44</context>
|
<context context-type="linenumber">46</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="034c2b473d0b76acbc938453375b13cb2491dc17" datatype="html">
|
<trans-unit id="034c2b473d0b76acbc938453375b13cb2491dc17" datatype="html">
|
||||||
@ -2366,7 +2366,7 @@
|
|||||||
<target state="translated">Ontwikkelde markten</target>
|
<target state="translated">Ontwikkelde markten</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">264</context>
|
<context context-type="linenumber">271</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
||||||
@ -2382,7 +2382,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">154</context>
|
<context context-type="linenumber">156</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
||||||
@ -2394,7 +2394,7 @@
|
|||||||
<target state="translated">Gemiddelde prijs per eenheid</target>
|
<target state="translated">Gemiddelde prijs per eenheid</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">65</context>
|
<context context-type="linenumber">67</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6dd84054c52e1edf631f37accb054de0c4071069" datatype="html">
|
<trans-unit id="6dd84054c52e1edf631f37accb054de0c4071069" datatype="html">
|
||||||
@ -2402,7 +2402,7 @@
|
|||||||
<target state="translated">Maximale prijs</target>
|
<target state="translated">Maximale prijs</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">97</context>
|
<context context-type="linenumber">99</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7233cd3a1ef8913fa5c6db7a29c88044646ceacc" datatype="html">
|
<trans-unit id="7233cd3a1ef8913fa5c6db7a29c88044646ceacc" datatype="html">
|
||||||
@ -2410,7 +2410,7 @@
|
|||||||
<target state="translated">Andere markten</target>
|
<target state="translated">Andere markten</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">282</context>
|
<context context-type="linenumber">289</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
||||||
@ -2422,7 +2422,7 @@
|
|||||||
<target state="translated">Opkomende markten</target>
|
<target state="translated">Opkomende markten</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">273</context>
|
<context context-type="linenumber">280</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
||||||
@ -2434,7 +2434,7 @@
|
|||||||
<target state="translated">Sector</target>
|
<target state="translated">Sector</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">169</context>
|
<context context-type="linenumber">171</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a43f25a9ac40e8e2441ff0be7a36b8e5d15534df" datatype="html">
|
<trans-unit id="a43f25a9ac40e8e2441ff0be7a36b8e5d15534df" datatype="html">
|
||||||
@ -2442,7 +2442,7 @@
|
|||||||
<target state="translated">Land</target>
|
<target state="translated">Land</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">181</context>
|
<context context-type="linenumber">183</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="b1c1c6a43da1ad3e41b7a6e3aa5dcc24226cf580" datatype="html">
|
<trans-unit id="b1c1c6a43da1ad3e41b7a6e3aa5dcc24226cf580" datatype="html">
|
||||||
@ -2450,7 +2450,7 @@
|
|||||||
<target state="translated">Minimale prijs</target>
|
<target state="translated">Minimale prijs</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">86</context>
|
<context context-type="linenumber">88</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="bf3df1f4eb29a071630eed167406c06f974480b2" datatype="html">
|
<trans-unit id="bf3df1f4eb29a071630eed167406c06f974480b2" datatype="html">
|
||||||
@ -2458,7 +2458,7 @@
|
|||||||
<target state="translated">Eerste aankoopdatum</target>
|
<target state="translated">Eerste aankoopdatum</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">127</context>
|
<context context-type="linenumber">129</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="add4cd82e3e38a3110fe67b3c7df56e9602644ee" datatype="html">
|
<trans-unit id="add4cd82e3e38a3110fe67b3c7df56e9602644ee" datatype="html">
|
||||||
@ -2466,7 +2466,7 @@
|
|||||||
<target state="translated">Transacties</target>
|
<target state="translated">Transacties</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">136</context>
|
<context context-type="linenumber">138</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="e34e2478d2d30c9d01758d01b7212411171b9bd5" datatype="html">
|
<trans-unit id="e34e2478d2d30c9d01758d01b7212411171b9bd5" datatype="html">
|
||||||
@ -2482,15 +2482,7 @@
|
|||||||
<target state="translated">Besparingen</target>
|
<target state="translated">Besparingen</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
|
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
|
||||||
<context context-type="linenumber">296</context>
|
<context context-type="linenumber">301</context>
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="1975246224413290232" datatype="html">
|
|
||||||
<source>Accumulating</source>
|
|
||||||
<target state="translated">Accumuleren</target>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
|
|
||||||
<context context-type="linenumber">39</context>
|
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2937311350146031865" datatype="html">
|
<trans-unit id="2937311350146031865" datatype="html">
|
||||||
@ -2498,7 +2490,7 @@
|
|||||||
<target state="translated">Aanvankelijk</target>
|
<target state="translated">Aanvankelijk</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
|
||||||
<context context-type="linenumber">57</context>
|
<context context-type="linenumber">58</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3441715041566940420" datatype="html">
|
<trans-unit id="3441715041566940420" datatype="html">
|
||||||
@ -2506,7 +2498,7 @@
|
|||||||
<target state="translated">Rente</target>
|
<target state="translated">Rente</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
|
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
|
||||||
<context context-type="linenumber">286</context>
|
<context context-type="linenumber">291</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5213771062241898526" datatype="html">
|
<trans-unit id="5213771062241898526" datatype="html">
|
||||||
@ -2514,11 +2506,11 @@
|
|||||||
<target state="translated">Storting</target>
|
<target state="translated">Storting</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context>
|
||||||
<context context-type="linenumber">132</context>
|
<context context-type="linenumber">137</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
|
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
|
||||||
<context context-type="linenumber">276</context>
|
<context context-type="linenumber">281</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6603000223840533819" datatype="html">
|
<trans-unit id="6603000223840533819" datatype="html">
|
||||||
@ -2526,7 +2518,7 @@
|
|||||||
<target state="translated">Huidige</target>
|
<target state="translated">Huidige</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
|
||||||
<context context-type="linenumber">58</context>
|
<context context-type="linenumber">59</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6762743264882388498" datatype="html">
|
<trans-unit id="6762743264882388498" datatype="html">
|
||||||
@ -2534,7 +2526,7 @@
|
|||||||
<target state="translated">Maandelijks</target>
|
<target state="translated">Maandelijks</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
|
||||||
<context context-type="linenumber">38</context>
|
<context context-type="linenumber">41</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8511b16abcf065252b350d64e337ba2447db3ffb" datatype="html">
|
<trans-unit id="8511b16abcf065252b350d64e337ba2447db3ffb" datatype="html">
|
||||||
@ -2574,11 +2566,11 @@
|
|||||||
<target state="translated">Filter op account of tag...</target>
|
<target state="translated">Filter op account of tag...</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
|
||||||
<context context-type="linenumber">136</context>
|
<context context-type="linenumber">137</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/holdings/holdings-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/holdings/holdings-page.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">88</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4550487415324294802" datatype="html">
|
<trans-unit id="4550487415324294802" datatype="html">
|
||||||
@ -2622,7 +2614,7 @@
|
|||||||
<target state="translated">Experimentele functies</target>
|
<target state="translated">Experimentele functies</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
<context context-type="linenumber">211</context>
|
<context context-type="linenumber">235</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1931353503905413384" datatype="html">
|
<trans-unit id="1931353503905413384" datatype="html">
|
||||||
@ -2630,15 +2622,15 @@
|
|||||||
<target state="translated">Benchmark</target>
|
<target state="translated">Benchmark</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context>
|
||||||
<context context-type="linenumber">116</context>
|
<context context-type="linenumber">110</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1b25c6e22f822e07a3e4d5aae4edc5b41fe083c2" datatype="html">
|
<trans-unit id="1b25c6e22f822e07a3e4d5aae4edc5b41fe083c2" datatype="html">
|
||||||
<source>Benchmarks</source>
|
<source>Benchmarks</source>
|
||||||
<target state="translated">Benchmarks</target>
|
<target state="translated">Benchmarks</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">4</context>
|
<context context-type="linenumber">123</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="44fcf77e86dc038202ebad6b46d1d833d60d781b" datatype="html">
|
<trans-unit id="44fcf77e86dc038202ebad6b46d1d833d60d781b" datatype="html">
|
||||||
@ -2670,7 +2662,287 @@
|
|||||||
<target state="new">Excluded from Analysis</target>
|
<target state="new">Excluded from Analysis</target>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">176</context>
|
<context context-type="linenumber">173</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="458363f8e413759aa9e3235a53fd0f64cc916395" datatype="html">
|
||||||
|
<source> If you retire today, you would be able to withdraw <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="font-weight-bold" >"/><x id="START_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="<gf-value class="d-inline-block" [currency]="user?.settings?.baseCurrency" [locale]="user?.settings?.locale" [value]="withdrawalRatePerYear?.toNumber()" >"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="</gf-value>"/> per year<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span >"/> or <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="font-weight-bold" >"/><x id="START_TAG_GF_VALUE_1" ctype="x-gf_value_1" equiv-text="<gf-value class="d-inline-block" [currency]="user?.settings?.baseCurrency" [locale]="user?.settings?.locale" [value]="withdrawalRatePerMonth?.toNumber()" >"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="</gf-value>"/> per month<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span >"/>, based on your total assets of <x id="START_TAG_GF_VALUE_2" ctype="x-gf_value_2" equiv-text="<gf-value class="d-inline-block" [currency]="user?.settings?.baseCurrency" [locale]="user?.settings?.locale" [value]="fireWealth?.toNumber()" >"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="</gf-value>"/> and a withdrawal rate of 4%. </source>
|
||||||
|
<target state="new"> If you retire today, you would be able to withdraw <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="font-weight-bold" >"/><x id="START_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="<gf-value class="d-inline-block" [currency]="user?.settings?.baseCurrency" [locale]="user?.settings?.locale" [value]="withdrawalRatePerYear?.toNumber()" >"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="</gf-value>"/> per year<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span >"/> or <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="font-weight-bold" >"/><x id="START_TAG_GF_VALUE_1" ctype="x-gf_value_1" equiv-text="<gf-value class="d-inline-block" [currency]="user?.settings?.baseCurrency" [locale]="user?.settings?.locale" [value]="withdrawalRatePerMonth?.toNumber()" >"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="</gf-value>"/> per month<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span >"/>, based on your total assets of <x id="START_TAG_GF_VALUE_2" ctype="x-gf_value_2" equiv-text="<gf-value class="d-inline-block" [currency]="user?.settings?.baseCurrency" [locale]="user?.settings?.locale" [value]="fireWealth?.toNumber()" >"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="</gf-value>"/> and a withdrawal rate of 4%. </target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/fire/fire-page.html</context>
|
||||||
|
<context context-type="linenumber">39,67</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="616064537937996961" datatype="html">
|
||||||
|
<source>Auto</source>
|
||||||
|
<target state="new">Auto</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
||||||
|
<context context-type="linenumber">45</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="bbe41ac2ea4a6c00ea941a41b33105048f8e9f13" datatype="html">
|
||||||
|
<source>Appearance</source>
|
||||||
|
<target state="new">Appearance</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
|
<context context-type="linenumber">195</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="5fb13fb4a8447e59cdf05dc196ade39c02a6f8aa" datatype="html">
|
||||||
|
<source>Auto</source>
|
||||||
|
<target state="new">Auto</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
|
<context context-type="linenumber">210</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="693d14f486a25e86bc515dfcfc4462d5201217ef" datatype="html">
|
||||||
|
<source>Light</source>
|
||||||
|
<target state="new">Light</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
|
<context context-type="linenumber">211</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="adb4562d2dbd3584370e44496969d58c511ecb63" datatype="html">
|
||||||
|
<source>Dark</source>
|
||||||
|
<target state="new">Dark</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
|
<context context-type="linenumber">212</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="112783260724635106" datatype="html">
|
||||||
|
<source>Total Amount</source>
|
||||||
|
<target state="new">Total Amount</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context>
|
||||||
|
<context context-type="linenumber">158</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="f1a355a1af2e818050a3af693ac8b521fa7edc5f" datatype="html">
|
||||||
|
<source>Portfolio Evolution</source>
|
||||||
|
<target state="new">Portfolio Evolution</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
|
||||||
|
<context context-type="linenumber">113</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8192718423057883427" datatype="html">
|
||||||
|
<source>Savings Rate</source>
|
||||||
|
<target state="translated">Spaarquote</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context>
|
||||||
|
<context context-type="linenumber">199</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="4086606389696938932" datatype="html">
|
||||||
|
<source>Account</source>
|
||||||
|
<target state="new">Account</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">4</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="4574987680940794089" datatype="html">
|
||||||
|
<source>Asset Class</source>
|
||||||
|
<target state="new">Asset Class</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">5</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8106025670158480144" datatype="html">
|
||||||
|
<source>Symbol</source>
|
||||||
|
<target state="new">Symbol</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">8</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="1825829511397926879" datatype="html">
|
||||||
|
<source>Tag</source>
|
||||||
|
<target state="new">Tag</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">9</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="787798817533231355" datatype="html">
|
||||||
|
<source>Cash</source>
|
||||||
|
<target state="new">Cash</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">12</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8431989971855844965" datatype="html">
|
||||||
|
<source>Commodity</source>
|
||||||
|
<target state="new">Commodity</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">13</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="1983771552391474467" datatype="html">
|
||||||
|
<source>Equity</source>
|
||||||
|
<target state="new">Equity</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">14</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="6124744839836623630" datatype="html">
|
||||||
|
<source>Fixed Income</source>
|
||||||
|
<target state="new">Fixed Income</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">15</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8432027249343784512" datatype="html">
|
||||||
|
<source>Real Estate</source>
|
||||||
|
<target state="new">Real Estate</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">16</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8977365084844053365" datatype="html">
|
||||||
|
<source>Bond</source>
|
||||||
|
<target state="new">Bond</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">19</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="2893204435511484886" datatype="html">
|
||||||
|
<source>Cryptocurrency</source>
|
||||||
|
<target state="new">Cryptocurrency</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">20</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="9071695492820527473" datatype="html">
|
||||||
|
<source>ETF</source>
|
||||||
|
<target state="new">ETF</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">21</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="5734784563242233466" datatype="html">
|
||||||
|
<source>Mutual Fund</source>
|
||||||
|
<target state="new">Mutual Fund</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">22</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="1270654249046226808" datatype="html">
|
||||||
|
<source>Precious Metal</source>
|
||||||
|
<target state="new">Precious Metal</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">23</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="1346519036036997811" datatype="html">
|
||||||
|
<source>Private Equity</source>
|
||||||
|
<target state="new">Private Equity</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">24</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="4613338085351943838" datatype="html">
|
||||||
|
<source>Stock</source>
|
||||||
|
<target state="new">Stock</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">25</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="6268646680388419543" datatype="html">
|
||||||
|
<source>Emergency Fund</source>
|
||||||
|
<target state="new">Emergency Fund</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">6</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8693603235657020323" datatype="html">
|
||||||
|
<source>Other</source>
|
||||||
|
<target state="new">Other</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">7</context>
|
||||||
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts</context>
|
||||||
|
<context context-type="linenumber">369</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="4893616715766810081" datatype="html">
|
||||||
|
<source>No data available</source>
|
||||||
|
<target state="new">No data available</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts</context>
|
||||||
|
<context context-type="linenumber">371</context>
|
||||||
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts</context>
|
||||||
|
<context context-type="linenumber">384</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="1228771048078164312" datatype="html">
|
||||||
|
<source>North America</source>
|
||||||
|
<target state="new">North America</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">31</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="1413778527796351850" datatype="html">
|
||||||
|
<source>Africa</source>
|
||||||
|
<target state="new">Africa</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">28</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="3345512471687795386" datatype="html">
|
||||||
|
<source>Asia</source>
|
||||||
|
<target state="new">Asia</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">29</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8350109327144196614" datatype="html">
|
||||||
|
<source>Europe</source>
|
||||||
|
<target state="new">Europe</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">30</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="3228811828827738441" datatype="html">
|
||||||
|
<source>Oceania</source>
|
||||||
|
<target state="new">Oceania</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">32</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="5957846001261659229" datatype="html">
|
||||||
|
<source>South America</source>
|
||||||
|
<target state="new">South America</target>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">33</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
|
@ -91,7 +91,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">214</context>
|
<context context-type="linenumber">218</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/portfolio-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/portfolio-page.html</context>
|
||||||
@ -326,7 +326,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">75</context>
|
<context context-type="linenumber">77</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="d7b35c384aecd25a516200d6921836374613dfe7" datatype="html">
|
<trans-unit id="d7b35c384aecd25a516200d6921836374613dfe7" datatype="html">
|
||||||
@ -386,7 +386,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">11</context>
|
<context context-type="linenumber">17</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5eebcc824c2d34b17abba8c9ef718c5ce118a5c8" datatype="html">
|
<trans-unit id="5eebcc824c2d34b17abba8c9ef718c5ce118a5c8" datatype="html">
|
||||||
@ -435,7 +435,7 @@
|
|||||||
<source>Please set your system message:</source>
|
<source>Please set your system message:</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.component.ts</context>
|
||||||
<context context-type="linenumber">199</context>
|
<context context-type="linenumber">202</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ec03f5c28b327fc7ecfc4b20a0a7cf14a75843ff" datatype="html">
|
<trans-unit id="ec03f5c28b327fc7ecfc4b20a0a7cf14a75843ff" datatype="html">
|
||||||
@ -449,21 +449,21 @@
|
|||||||
<source>per User</source>
|
<source>per User</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">15</context>
|
<context context-type="linenumber">26</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5067ede95804e0e0e2b078747848367a7373cc9b" datatype="html">
|
<trans-unit id="5067ede95804e0e0e2b078747848367a7373cc9b" datatype="html">
|
||||||
<source>Gather Recent Data</source>
|
<source>Gather Recent Data</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">33</context>
|
<context context-type="linenumber">44</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="27e27f3f7338baefacfd5668bad03ece9c5955df" datatype="html">
|
<trans-unit id="27e27f3f7338baefacfd5668bad03ece9c5955df" datatype="html">
|
||||||
<source>Gather All Data</source>
|
<source>Gather All Data</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">46</context>
|
<context context-type="linenumber">57</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="cc65b67b46b69cf06ff1f16a909e61612c9d57b8" datatype="html">
|
<trans-unit id="cc65b67b46b69cf06ff1f16a909e61612c9d57b8" datatype="html">
|
||||||
@ -474,70 +474,70 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">60</context>
|
<context context-type="linenumber">71</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="37ff0697aa81b2777250c483d2ce1ebdaa5ab042" datatype="html">
|
<trans-unit id="37ff0697aa81b2777250c483d2ce1ebdaa5ab042" datatype="html">
|
||||||
<source>Exchange Rates</source>
|
<source>Exchange Rates</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">67</context>
|
<context context-type="linenumber">78</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4567a660a7f67e254bbf13219906843e34d9f807" datatype="html">
|
<trans-unit id="4567a660a7f67e254bbf13219906843e34d9f807" datatype="html">
|
||||||
<source>Add Currency</source>
|
<source>Add Currency</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">106</context>
|
<context context-type="linenumber">117</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="860e5f056b59410ec8db65cb53955505c6931752" datatype="html">
|
<trans-unit id="860e5f056b59410ec8db65cb53955505c6931752" datatype="html">
|
||||||
<source>System Message</source>
|
<source>System Message</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">112</context>
|
<context context-type="linenumber">133</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="657028d5fc9c3da8f2d667b6b15cd0df8b9a3729" datatype="html">
|
<trans-unit id="657028d5fc9c3da8f2d667b6b15cd0df8b9a3729" datatype="html">
|
||||||
<source>Set Message</source>
|
<source>Set Message</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">134</context>
|
<context context-type="linenumber">155</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7fd64c34428887e4cd56d05534b89c100b8544ad" datatype="html">
|
<trans-unit id="7fd64c34428887e4cd56d05534b89c100b8544ad" datatype="html">
|
||||||
<source>Read-only Mode</source>
|
<source>Read-only Mode</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">139</context>
|
<context context-type="linenumber">160</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="e698b03c34b459b1b006d7f0473a49b9fcf5dfc1" datatype="html">
|
<trans-unit id="e698b03c34b459b1b006d7f0473a49b9fcf5dfc1" datatype="html">
|
||||||
<source>Coupons</source>
|
<source>Coupons</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">152</context>
|
<context context-type="linenumber">173</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="f6755cff4957d5c3c89bafce5651f1b6fa2b1fd9" datatype="html">
|
<trans-unit id="f6755cff4957d5c3c89bafce5651f1b6fa2b1fd9" datatype="html">
|
||||||
<source>Add</source>
|
<source>Add</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">187</context>
|
<context context-type="linenumber">208</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="e799e6b926557f0098f41888cdf8df868eff3d47" datatype="html">
|
<trans-unit id="e799e6b926557f0098f41888cdf8df868eff3d47" datatype="html">
|
||||||
<source>Housekeeping</source>
|
<source>Housekeeping</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">194</context>
|
<context context-type="linenumber">215</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="c7ac907e52a7ce2ac70b1786eb5f403ce306ce1f" datatype="html">
|
<trans-unit id="c7ac907e52a7ce2ac70b1786eb5f403ce306ce1f" datatype="html">
|
||||||
<source>Flush Cache</source>
|
<source>Flush Cache</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">198</context>
|
<context context-type="linenumber">219</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2817099043823177227" datatype="html">
|
<trans-unit id="2817099043823177227" datatype="html">
|
||||||
@ -855,102 +855,102 @@
|
|||||||
<source>Investment</source>
|
<source>Investment</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">40</context>
|
<context context-type="linenumber">37</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">117</context>
|
<context context-type="linenumber">119</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a64cd8d0131a583e3de081c9e6458af7aecdafbc" datatype="html">
|
<trans-unit id="a64cd8d0131a583e3de081c9e6458af7aecdafbc" datatype="html">
|
||||||
<source>Absolute Gross Performance</source>
|
<source>Absolute Gross Performance</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">51</context>
|
<context context-type="linenumber">48</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="e98a980fc12d6479b9c61431b905ce0c8ddc3ec5" datatype="html">
|
<trans-unit id="e98a980fc12d6479b9c61431b905ce0c8ddc3ec5" datatype="html">
|
||||||
<source>Gross Performance (TWR)</source>
|
<source>Gross Performance (TWR)</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">62</context>
|
<context context-type="linenumber">59</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="e5c369abfbe1bd70f577aa03b2679797e38d7590" datatype="html">
|
<trans-unit id="e5c369abfbe1bd70f577aa03b2679797e38d7590" datatype="html">
|
||||||
<source> Fees for <x id="INTERPOLATION" equiv-text="{{ summary?.ordersCount }}"/> <x id="ICU" equiv-text="{summary?.ordersCount, plural, =1 {transaction} other {transactions}}"/> </source>
|
<source> Fees for <x id="INTERPOLATION" equiv-text="{{ summary?.ordersCount }}"/> <x id="ICU" equiv-text="{summary?.ordersCount, plural, =1 {transaction} other {transactions}}"/> </source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">77,80</context>
|
<context context-type="linenumber">74,77</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="272c7fd98af55bfa5b9d579176f1cfa25cd5489f" datatype="html">
|
<trans-unit id="272c7fd98af55bfa5b9d579176f1cfa25cd5489f" datatype="html">
|
||||||
<source>{VAR_PLURAL, plural, =1 {transaction} other {transactions}}</source>
|
<source>{VAR_PLURAL, plural, =1 {transaction} other {transactions}}</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">78,79</context>
|
<context context-type="linenumber">75,76</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="de518fa0cd3a454e84102c0d9984c4a3cfdb590d" datatype="html">
|
<trans-unit id="de518fa0cd3a454e84102c0d9984c4a3cfdb590d" datatype="html">
|
||||||
<source>Absolute Net Performance</source>
|
<source>Absolute Net Performance</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">95</context>
|
<context context-type="linenumber">92</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ba987f3e4d4458aa36c61c03bedb60d98ad20393" datatype="html">
|
<trans-unit id="ba987f3e4d4458aa36c61c03bedb60d98ad20393" datatype="html">
|
||||||
<source>Net Performance (TWR)</source>
|
<source>Net Performance (TWR)</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">106</context>
|
<context context-type="linenumber">103</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="f159ffb7a09c8c1fe7913ef063462db50ddcd878" datatype="html">
|
<trans-unit id="f159ffb7a09c8c1fe7913ef063462db50ddcd878" datatype="html">
|
||||||
<source>Total Assets</source>
|
<source>Total Assets</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">122</context>
|
<context context-type="linenumber">119</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="59d87bdb983dda90c1e86d086430552ec05c7a4d" datatype="html">
|
<trans-unit id="59d87bdb983dda90c1e86d086430552ec05c7a4d" datatype="html">
|
||||||
<source>Valuables</source>
|
<source>Valuables</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">134</context>
|
<context context-type="linenumber">131</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2201b2004bc4997a66f6f8ea2511a4e1311f3de1" datatype="html">
|
<trans-unit id="2201b2004bc4997a66f6f8ea2511a4e1311f3de1" datatype="html">
|
||||||
<source>Emergency Fund</source>
|
<source>Emergency Fund</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">145</context>
|
<context context-type="linenumber">142</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8cce9d03787606e0052d19c2ae7e7fa5ff785e94" datatype="html">
|
<trans-unit id="8cce9d03787606e0052d19c2ae7e7fa5ff785e94" datatype="html">
|
||||||
<source>Buying Power</source>
|
<source>Buying Power</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">165</context>
|
<context context-type="linenumber">162</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="67251f04518ae452230c68a748b3fa2838b4db74" datatype="html">
|
<trans-unit id="67251f04518ae452230c68a748b3fa2838b4db74" datatype="html">
|
||||||
<source>Net Worth</source>
|
<source>Net Worth</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">190</context>
|
<context context-type="linenumber">187</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="e1b20ce1622d86ae0a75a3555a1a9ae7c2c60e58" datatype="html">
|
<trans-unit id="e1b20ce1622d86ae0a75a3555a1a9ae7c2c60e58" datatype="html">
|
||||||
<source>Annualized Performance</source>
|
<source>Annualized Performance</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">201</context>
|
<context context-type="linenumber">198</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="d3aa83bd247983dd056a62f56ffb25269bd98d47" datatype="html">
|
<trans-unit id="d3aa83bd247983dd056a62f56ffb25269bd98d47" datatype="html">
|
||||||
<source>Dividend</source>
|
<source>Dividend</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">217</context>
|
<context context-type="linenumber">214</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6785405835169448749" datatype="html">
|
<trans-unit id="6785405835169448749" datatype="html">
|
||||||
@ -964,7 +964,7 @@
|
|||||||
<source>Sectors</source>
|
<source>Sectors</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">187</context>
|
<context context-type="linenumber">189</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
||||||
@ -975,14 +975,14 @@
|
|||||||
<source>Countries</source>
|
<source>Countries</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">198</context>
|
<context context-type="linenumber">201</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="cafc87479686947e2590b9f588a88040aeaf660b" datatype="html">
|
<trans-unit id="cafc87479686947e2590b9f588a88040aeaf660b" datatype="html">
|
||||||
<source>Tags</source>
|
<source>Tags</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">234</context>
|
<context context-type="linenumber">238</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
||||||
@ -993,7 +993,7 @@
|
|||||||
<source>Report Data Glitch</source>
|
<source>Report Data Glitch</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">249</context>
|
<context context-type="linenumber">253</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
|
<trans-unit id="2ee26d58f2707416e636887111d5603b35346c4a" datatype="html">
|
||||||
@ -1005,9 +1005,13 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3cc9c2ae277393b3946b38c088dabff671b1ee1b" datatype="html">
|
<trans-unit id="3cc9c2ae277393b3946b38c088dabff671b1ee1b" datatype="html">
|
||||||
<source>Performance</source>
|
<source>Performance</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context>
|
||||||
|
<context context-type="linenumber">4</context>
|
||||||
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">55</context>
|
<context context-type="linenumber">57</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/positions-table/positions-table.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/positions-table/positions-table.component.html</context>
|
||||||
@ -1145,35 +1149,35 @@
|
|||||||
<source>Please enter your coupon code:</source>
|
<source>Please enter your coupon code:</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
||||||
<context context-type="linenumber">225</context>
|
<context context-type="linenumber">226</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4420880039966769543" datatype="html">
|
<trans-unit id="4420880039966769543" datatype="html">
|
||||||
<source>Could not redeem coupon code</source>
|
<source>Could not redeem coupon code</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
||||||
<context context-type="linenumber">235</context>
|
<context context-type="linenumber">236</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4819099731531004979" datatype="html">
|
<trans-unit id="4819099731531004979" datatype="html">
|
||||||
<source>Coupon code has been redeemed</source>
|
<source>Coupon code has been redeemed</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
||||||
<context context-type="linenumber">247</context>
|
<context context-type="linenumber">248</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7967484035994732534" datatype="html">
|
<trans-unit id="7967484035994732534" datatype="html">
|
||||||
<source>Reload</source>
|
<source>Reload</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
||||||
<context context-type="linenumber">248</context>
|
<context context-type="linenumber">249</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7963559562180316948" datatype="html">
|
<trans-unit id="7963559562180316948" datatype="html">
|
||||||
<source>Do you really want to remove this sign in method?</source>
|
<source>Do you really want to remove this sign in method?</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
||||||
<context context-type="linenumber">294</context>
|
<context context-type="linenumber">295</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="29881a45dafbe5aa05cd9d0441a4c0c2fb06df92" datatype="html">
|
<trans-unit id="29881a45dafbe5aa05cd9d0441a4c0c2fb06df92" datatype="html">
|
||||||
@ -1272,21 +1276,21 @@
|
|||||||
<source>Sign in with fingerprint</source>
|
<source>Sign in with fingerprint</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
<context context-type="linenumber">196</context>
|
<context context-type="linenumber">220</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="83c4d4d764d2e2725ab8e919ec16ac400e1f290a" datatype="html">
|
<trans-unit id="83c4d4d764d2e2725ab8e919ec16ac400e1f290a" datatype="html">
|
||||||
<source>User ID</source>
|
<source>User ID</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
<context context-type="linenumber">223</context>
|
<context context-type="linenumber">247</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="9021c579c084e68d9db06a569d76f024111c6c54" datatype="html">
|
<trans-unit id="9021c579c084e68d9db06a569d76f024111c6c54" datatype="html">
|
||||||
<source>Granted Access</source>
|
<source>Granted Access</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
<context context-type="linenumber">232</context>
|
<context context-type="linenumber">256</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5e41f1b4c46ad9e0a9bc83fa36445483aa5cc324" datatype="html">
|
<trans-unit id="5e41f1b4c46ad9e0a9bc83fa36445483aa5cc324" datatype="html">
|
||||||
@ -1472,49 +1476,49 @@
|
|||||||
<source>By Currency</source>
|
<source>By Currency</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">66</context>
|
<context context-type="linenumber">67</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8288ff761f2d259625d2e5a3d96db727926d9cd4" datatype="html">
|
<trans-unit id="8288ff761f2d259625d2e5a3d96db727926d9cd4" datatype="html">
|
||||||
<source>By Asset Class</source>
|
<source>By Asset Class</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">94</context>
|
<context context-type="linenumber">96</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="b64539bb7815eb3275b55ad723d3897fc6ba8d23" datatype="html">
|
<trans-unit id="b64539bb7815eb3275b55ad723d3897fc6ba8d23" datatype="html">
|
||||||
<source>By Holding</source>
|
<source>By Holding</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">122</context>
|
<context context-type="linenumber">125</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="9f86714c9a6b74e13c96ab02102ce40c34fe13b9" datatype="html">
|
<trans-unit id="9f86714c9a6b74e13c96ab02102ce40c34fe13b9" datatype="html">
|
||||||
<source>By Sector</source>
|
<source>By Sector</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">150</context>
|
<context context-type="linenumber">154</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7017e0e26b53ef322c3e3bbf95f06a85487a12b2" datatype="html">
|
<trans-unit id="7017e0e26b53ef322c3e3bbf95f06a85487a12b2" datatype="html">
|
||||||
<source>By Continent</source>
|
<source>By Continent</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">179</context>
|
<context context-type="linenumber">184</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="f27e9dd8de80176286e02312e694cb8d1e485a5d" datatype="html">
|
<trans-unit id="f27e9dd8de80176286e02312e694cb8d1e485a5d" datatype="html">
|
||||||
<source>By Country</source>
|
<source>By Country</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">207</context>
|
<context context-type="linenumber">213</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="85780db87ac6c9f202615ac63754551c061e7236" datatype="html">
|
<trans-unit id="85780db87ac6c9f202615ac63754551c061e7236" datatype="html">
|
||||||
<source>Regions</source>
|
<source>Regions</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">238</context>
|
<context context-type="linenumber">245</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
||||||
@ -1543,21 +1547,21 @@
|
|||||||
<source>Investment Timeline</source>
|
<source>Investment Timeline</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
|
||||||
<context context-type="linenumber">105</context>
|
<context context-type="linenumber">142</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html">
|
<trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html">
|
||||||
<source>Top</source>
|
<source>Top</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
|
||||||
<context context-type="linenumber">26</context>
|
<context context-type="linenumber">34</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
|
<trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
|
||||||
<source>Bottom</source>
|
<source>Bottom</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
|
||||||
<context context-type="linenumber">62</context>
|
<context context-type="linenumber">70</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5857197365507636437" datatype="html">
|
<trans-unit id="5857197365507636437" datatype="html">
|
||||||
@ -1589,7 +1593,7 @@
|
|||||||
<source>4% Rule</source>
|
<source>4% Rule</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/fire/fire-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/fire/fire-page.html</context>
|
||||||
<context context-type="linenumber">20</context>
|
<context context-type="linenumber">21</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="803941175683258052" datatype="html">
|
<trans-unit id="803941175683258052" datatype="html">
|
||||||
@ -1751,7 +1755,7 @@
|
|||||||
<source>Quantity</source>
|
<source>Quantity</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">107</context>
|
<context context-type="linenumber">109</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
||||||
@ -1799,7 +1803,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">145</context>
|
<context context-type="linenumber">147</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
||||||
@ -1824,7 +1828,7 @@
|
|||||||
<source>Import has been completed</source>
|
<source>Import has been completed</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/transactions-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/transactions-page.component.ts</context>
|
||||||
<context context-type="linenumber">337</context>
|
<context context-type="linenumber">338,337</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5080775557941296581" datatype="html">
|
<trans-unit id="5080775557941296581" datatype="html">
|
||||||
@ -1838,7 +1842,7 @@
|
|||||||
<source>Portfolio</source>
|
<source>Portfolio</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context>
|
||||||
<context context-type="linenumber">107</context>
|
<context context-type="linenumber">101</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page-routing.module.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page-routing.module.ts</context>
|
||||||
@ -2043,10 +2047,6 @@
|
|||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="313fcf0f8dac5ff5800a3e6bd67cb1955089ccca" datatype="html">
|
<trans-unit id="313fcf0f8dac5ff5800a3e6bd67cb1955089ccca" datatype="html">
|
||||||
<source>Beta</source>
|
<source>Beta</source>
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context>
|
|
||||||
<context context-type="linenumber">5</context>
|
|
||||||
</context-group>
|
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
<context context-type="linenumber">119</context>
|
<context context-type="linenumber">119</context>
|
||||||
@ -2070,7 +2070,7 @@
|
|||||||
<source>Data Management</source>
|
<source>Data Management</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">20</context>
|
<context context-type="linenumber">31</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3ccabfc3dc288eaa2355ba43298c739f85951ec3" datatype="html">
|
<trans-unit id="3ccabfc3dc288eaa2355ba43298c739f85951ec3" datatype="html">
|
||||||
@ -2109,14 +2109,14 @@
|
|||||||
<source>Change</source>
|
<source>Change</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">44</context>
|
<context context-type="linenumber">46</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="034c2b473d0b76acbc938453375b13cb2491dc17" datatype="html">
|
<trans-unit id="034c2b473d0b76acbc938453375b13cb2491dc17" datatype="html">
|
||||||
<source>Developed Markets</source>
|
<source>Developed Markets</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">264</context>
|
<context context-type="linenumber">271</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
||||||
@ -2131,7 +2131,7 @@
|
|||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">154</context>
|
<context context-type="linenumber">156</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
|
||||||
@ -2142,21 +2142,21 @@
|
|||||||
<source>Average Unit Price</source>
|
<source>Average Unit Price</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">65</context>
|
<context context-type="linenumber">67</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6dd84054c52e1edf631f37accb054de0c4071069" datatype="html">
|
<trans-unit id="6dd84054c52e1edf631f37accb054de0c4071069" datatype="html">
|
||||||
<source>Maximum Price</source>
|
<source>Maximum Price</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">97</context>
|
<context context-type="linenumber">99</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="7233cd3a1ef8913fa5c6db7a29c88044646ceacc" datatype="html">
|
<trans-unit id="7233cd3a1ef8913fa5c6db7a29c88044646ceacc" datatype="html">
|
||||||
<source>Other Markets</source>
|
<source>Other Markets</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">282</context>
|
<context context-type="linenumber">289</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
||||||
@ -2167,7 +2167,7 @@
|
|||||||
<source>Emerging Markets</source>
|
<source>Emerging Markets</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.html</context>
|
||||||
<context context-type="linenumber">273</context>
|
<context context-type="linenumber">280</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/public/public-page.html</context>
|
||||||
@ -2178,35 +2178,35 @@
|
|||||||
<source>Sector</source>
|
<source>Sector</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">169</context>
|
<context context-type="linenumber">171</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="a43f25a9ac40e8e2441ff0be7a36b8e5d15534df" datatype="html">
|
<trans-unit id="a43f25a9ac40e8e2441ff0be7a36b8e5d15534df" datatype="html">
|
||||||
<source>Country</source>
|
<source>Country</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">181</context>
|
<context context-type="linenumber">183</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="b1c1c6a43da1ad3e41b7a6e3aa5dcc24226cf580" datatype="html">
|
<trans-unit id="b1c1c6a43da1ad3e41b7a6e3aa5dcc24226cf580" datatype="html">
|
||||||
<source>Minimum Price</source>
|
<source>Minimum Price</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">86</context>
|
<context context-type="linenumber">88</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="bf3df1f4eb29a071630eed167406c06f974480b2" datatype="html">
|
<trans-unit id="bf3df1f4eb29a071630eed167406c06f974480b2" datatype="html">
|
||||||
<source>First Buy Date</source>
|
<source>First Buy Date</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">127</context>
|
<context context-type="linenumber">129</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="add4cd82e3e38a3110fe67b3c7df56e9602644ee" datatype="html">
|
<trans-unit id="add4cd82e3e38a3110fe67b3c7df56e9602644ee" datatype="html">
|
||||||
<source>Transactions</source>
|
<source>Transactions</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
|
||||||
<context context-type="linenumber">136</context>
|
<context context-type="linenumber">138</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="e34e2478d2d30c9d01758d01b7212411171b9bd5" datatype="html">
|
<trans-unit id="e34e2478d2d30c9d01758d01b7212411171b9bd5" datatype="html">
|
||||||
@ -2220,53 +2220,46 @@
|
|||||||
<source>Savings</source>
|
<source>Savings</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
|
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
|
||||||
<context context-type="linenumber">296</context>
|
<context context-type="linenumber">301</context>
|
||||||
</context-group>
|
|
||||||
</trans-unit>
|
|
||||||
<trans-unit id="1975246224413290232" datatype="html">
|
|
||||||
<source>Accumulating</source>
|
|
||||||
<context-group purpose="location">
|
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
|
|
||||||
<context context-type="linenumber">39</context>
|
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="2937311350146031865" datatype="html">
|
<trans-unit id="2937311350146031865" datatype="html">
|
||||||
<source>Initial</source>
|
<source>Initial</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
|
||||||
<context context-type="linenumber">57</context>
|
<context context-type="linenumber">58</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="3441715041566940420" datatype="html">
|
<trans-unit id="3441715041566940420" datatype="html">
|
||||||
<source>Interest</source>
|
<source>Interest</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
|
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
|
||||||
<context context-type="linenumber">286</context>
|
<context context-type="linenumber">291</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="5213771062241898526" datatype="html">
|
<trans-unit id="5213771062241898526" datatype="html">
|
||||||
<source>Deposit</source>
|
<source>Deposit</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context>
|
||||||
<context context-type="linenumber">132</context>
|
<context context-type="linenumber">137</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
|
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
|
||||||
<context context-type="linenumber">276</context>
|
<context context-type="linenumber">281</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6603000223840533819" datatype="html">
|
<trans-unit id="6603000223840533819" datatype="html">
|
||||||
<source>Current</source>
|
<source>Current</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
|
||||||
<context context-type="linenumber">58</context>
|
<context context-type="linenumber">59</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="6762743264882388498" datatype="html">
|
<trans-unit id="6762743264882388498" datatype="html">
|
||||||
<source>Monthly</source>
|
<source>Monthly</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
|
||||||
<context context-type="linenumber">38</context>
|
<context context-type="linenumber">41</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="8511b16abcf065252b350d64e337ba2447db3ffb" datatype="html">
|
<trans-unit id="8511b16abcf065252b350d64e337ba2447db3ffb" datatype="html">
|
||||||
@ -2301,11 +2294,11 @@
|
|||||||
<source>Filter by account or tag...</source>
|
<source>Filter by account or tag...</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
|
||||||
<context context-type="linenumber">136</context>
|
<context context-type="linenumber">137</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/holdings/holdings-page.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/holdings/holdings-page.component.ts</context>
|
||||||
<context context-type="linenumber">87</context>
|
<context context-type="linenumber">88</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="4550487415324294802" datatype="html">
|
<trans-unit id="4550487415324294802" datatype="html">
|
||||||
@ -2344,21 +2337,21 @@
|
|||||||
<source>Experimental Features</source>
|
<source>Experimental Features</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
<context context-type="linenumber">211</context>
|
<context context-type="linenumber">235</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1931353503905413384" datatype="html">
|
<trans-unit id="1931353503905413384" datatype="html">
|
||||||
<source>Benchmark</source>
|
<source>Benchmark</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context>
|
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.ts</context>
|
||||||
<context context-type="linenumber">116</context>
|
<context context-type="linenumber">110</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="1b25c6e22f822e07a3e4d5aae4edc5b41fe083c2" datatype="html">
|
<trans-unit id="1b25c6e22f822e07a3e4d5aae4edc5b41fe083c2" datatype="html">
|
||||||
<source>Benchmarks</source>
|
<source>Benchmarks</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/benchmark-comparator/benchmark-comparator.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
|
||||||
<context context-type="linenumber">4</context>
|
<context context-type="linenumber">123</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="44fcf77e86dc038202ebad6b46d1d833d60d781b" datatype="html">
|
<trans-unit id="44fcf77e86dc038202ebad6b46d1d833d60d781b" datatype="html">
|
||||||
@ -2379,7 +2372,7 @@
|
|||||||
<source>Excluded from Analysis</source>
|
<source>Excluded from Analysis</source>
|
||||||
<context-group purpose="location">
|
<context-group purpose="location">
|
||||||
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
<context context-type="sourcefile">apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html</context>
|
||||||
<context context-type="linenumber">176</context>
|
<context context-type="linenumber">173</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="ac598d664f86ba5783915d65f2664a7f38a9d23a" datatype="html">
|
<trans-unit id="ac598d664f86ba5783915d65f2664a7f38a9d23a" datatype="html">
|
||||||
@ -2389,6 +2382,252 @@
|
|||||||
<context context-type="linenumber">25</context>
|
<context context-type="linenumber">25</context>
|
||||||
</context-group>
|
</context-group>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="458363f8e413759aa9e3235a53fd0f64cc916395" datatype="html">
|
||||||
|
<source> If you retire today, you would be able to withdraw <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="font-weight-bold" >"/><x id="START_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="<gf-value class="d-inline-block" [currency]="user?.settings?.baseCurrency" [locale]="user?.settings?.locale" [value]="withdrawalRatePerYear?.toNumber()" >"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="</gf-value>"/> per year<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span >"/> or <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span class="font-weight-bold" >"/><x id="START_TAG_GF_VALUE_1" ctype="x-gf_value_1" equiv-text="<gf-value class="d-inline-block" [currency]="user?.settings?.baseCurrency" [locale]="user?.settings?.locale" [value]="withdrawalRatePerMonth?.toNumber()" >"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="</gf-value>"/> per month<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</span >"/>, based on your total assets of <x id="START_TAG_GF_VALUE_2" ctype="x-gf_value_2" equiv-text="<gf-value class="d-inline-block" [currency]="user?.settings?.baseCurrency" [locale]="user?.settings?.locale" [value]="fireWealth?.toNumber()" >"/><x id="CLOSE_TAG_GF_VALUE" ctype="x-gf_value" equiv-text="</gf-value>"/> and a withdrawal rate of 4%. </source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/fire/fire-page.html</context>
|
||||||
|
<context context-type="linenumber">39,67</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="5fb13fb4a8447e59cdf05dc196ade39c02a6f8aa" datatype="html">
|
||||||
|
<source>Auto</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
|
<context context-type="linenumber">210</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="616064537937996961" datatype="html">
|
||||||
|
<source>Auto</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.component.ts</context>
|
||||||
|
<context context-type="linenumber">45</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="693d14f486a25e86bc515dfcfc4462d5201217ef" datatype="html">
|
||||||
|
<source>Light</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
|
<context context-type="linenumber">211</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="adb4562d2dbd3584370e44496969d58c511ecb63" datatype="html">
|
||||||
|
<source>Dark</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
|
<context context-type="linenumber">212</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="bbe41ac2ea4a6c00ea941a41b33105048f8e9f13" datatype="html">
|
||||||
|
<source>Appearance</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/pages/account/account-page.html</context>
|
||||||
|
<context context-type="linenumber">195</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="112783260724635106" datatype="html">
|
||||||
|
<source>Total Amount</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context>
|
||||||
|
<context context-type="linenumber">158</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="f1a355a1af2e818050a3af693ac8b521fa7edc5f" datatype="html">
|
||||||
|
<source>Portfolio Evolution</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
|
||||||
|
<context context-type="linenumber">113</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8192718423057883427" datatype="html">
|
||||||
|
<source>Savings Rate</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context>
|
||||||
|
<context context-type="linenumber">199</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="1270654249046226808" datatype="html">
|
||||||
|
<source>Precious Metal</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">23</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="1825829511397926879" datatype="html">
|
||||||
|
<source>Tag</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">9</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="1983771552391474467" datatype="html">
|
||||||
|
<source>Equity</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">14</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8432027249343784512" datatype="html">
|
||||||
|
<source>Real Estate</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">16</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="2893204435511484886" datatype="html">
|
||||||
|
<source>Cryptocurrency</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">20</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="4086606389696938932" datatype="html">
|
||||||
|
<source>Account</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">4</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="4613338085351943838" datatype="html">
|
||||||
|
<source>Stock</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">25</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="1346519036036997811" datatype="html">
|
||||||
|
<source>Private Equity</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">24</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="4574987680940794089" datatype="html">
|
||||||
|
<source>Asset Class</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">5</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="5734784563242233466" datatype="html">
|
||||||
|
<source>Mutual Fund</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">22</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="787798817533231355" datatype="html">
|
||||||
|
<source>Cash</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">12</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8106025670158480144" datatype="html">
|
||||||
|
<source>Symbol</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">8</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8431989971855844965" datatype="html">
|
||||||
|
<source>Commodity</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">13</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8977365084844053365" datatype="html">
|
||||||
|
<source>Bond</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">19</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="9071695492820527473" datatype="html">
|
||||||
|
<source>ETF</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">21</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="6124744839836623630" datatype="html">
|
||||||
|
<source>Fixed Income</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">15</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="4893616715766810081" datatype="html">
|
||||||
|
<source>No data available</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts</context>
|
||||||
|
<context context-type="linenumber">371</context>
|
||||||
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts</context>
|
||||||
|
<context context-type="linenumber">384</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="6268646680388419543" datatype="html">
|
||||||
|
<source>Emergency Fund</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">6</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8693603235657020323" datatype="html">
|
||||||
|
<source>Other</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">7</context>
|
||||||
|
</context-group>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts</context>
|
||||||
|
<context context-type="linenumber">369</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="1228771048078164312" datatype="html">
|
||||||
|
<source>North America</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">31</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="1413778527796351850" datatype="html">
|
||||||
|
<source>Africa</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">28</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="3228811828827738441" datatype="html">
|
||||||
|
<source>Oceania</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">32</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="3345512471687795386" datatype="html">
|
||||||
|
<source>Asia</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">29</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="5957846001261659229" datatype="html">
|
||||||
|
<source>South America</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">33</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="8350109327144196614" datatype="html">
|
||||||
|
<source>Europe</source>
|
||||||
|
<context-group purpose="location">
|
||||||
|
<context context-type="sourcefile">libs/ui/src/lib/i18n.ts</context>
|
||||||
|
<context context-type="linenumber">30</context>
|
||||||
|
</context-group>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
@ -17,7 +17,8 @@ $mat-css-light-theme-selector: '.is-light-theme';
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--dark-background: rgb(39, 39, 39);
|
--dark-background: rgb(25, 25, 25);
|
||||||
|
--font-family-sans-serif: Roboto, 'Helvetica Neue', sans-serif;
|
||||||
--light-background: rgb(255, 255, 255);
|
--light-background: rgb(255, 255, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +97,21 @@ body {
|
|||||||
color: rgba(var(--light-primary-text));
|
color: rgba(var(--light-primary-text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.with-placeholder-as-option {
|
||||||
|
.mat-select-placeholder {
|
||||||
|
color: rgba(var(--light-primary-text));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mat-select-disabled {
|
||||||
|
.mat-select-placeholder {
|
||||||
|
color: rgba(
|
||||||
|
var(--palette-foreground-disabled-text-dark),
|
||||||
|
var(--palette-foreground-disabled-text-dark-alpha)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,6 +162,10 @@ ngx-skeleton-loader {
|
|||||||
@include gf-table;
|
@include gf-table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lead {
|
||||||
|
font-weight: unset;
|
||||||
|
}
|
||||||
|
|
||||||
.mat-card {
|
.mat-card {
|
||||||
&:not([class*='mat-elevation-z']) {
|
&:not([class*='mat-elevation-z']) {
|
||||||
border: 1px solid rgba(var(--dark-dividers));
|
border: 1px solid rgba(var(--dark-dividers));
|
||||||
@ -223,3 +243,18 @@ ngx-skeleton-loader {
|
|||||||
.with-info-message {
|
.with-info-message {
|
||||||
height: calc(100vh - 5rem - 3.5rem) !important;
|
height: calc(100vh - 5rem - 3.5rem) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.with-placeholder-as-option {
|
||||||
|
.mat-select-placeholder {
|
||||||
|
color: rgba(var(--dark-primary-text));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mat-select-disabled {
|
||||||
|
.mat-select-placeholder {
|
||||||
|
color: rgba(
|
||||||
|
var(--palette-foreground-disabled-text),
|
||||||
|
var(--palette-foreground-disabled-text-alpha)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
28
apps/ui-e2e/project.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "apps/ui-e2e/src",
|
||||||
|
"projectType": "application",
|
||||||
|
"targets": {
|
||||||
|
"e2e": {
|
||||||
|
"executor": "@nrwl/cypress:cypress",
|
||||||
|
"options": {
|
||||||
|
"cypressConfig": "apps/ui-e2e/cypress.json",
|
||||||
|
"devServerTarget": "ui:storybook",
|
||||||
|
"tsConfig": "apps/ui-e2e/tsconfig.json"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"ci": {
|
||||||
|
"devServerTarget": "ui:storybook:ci"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nrwl/linter:eslint",
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": ["apps/ui-e2e/**/*.{js,ts}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [],
|
||||||
|
"implicitDependencies": ["ui"]
|
||||||
|
}
|
22
libs/common/project.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"sourceRoot": "libs/common/src",
|
||||||
|
"projectType": "library",
|
||||||
|
"targets": {
|
||||||
|
"lint": {
|
||||||
|
"executor": "@nrwl/linter:eslint",
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": ["libs/common/**/*.ts"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"executor": "@nrwl/jest:jest",
|
||||||
|
"outputs": ["{workspaceRoot}/coverage/libs/common"],
|
||||||
|
"options": {
|
||||||
|
"jestConfig": "libs/common/jest.config.ts",
|
||||||
|
"passWithNoTests": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|