Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
fefee11301 | |||
40836b745b | |||
07eabac059 | |||
48b412cfb8 | |||
b62488628c | |||
982c71c728 | |||
5aa16a3779 | |||
93de25e5b6 | |||
9acdb41aa2 |
@ -12,6 +12,12 @@
|
||||
"importOrder": ["^@ghostfolio/(.*)$", "<THIRD_PARTY_MODULES>", "^[./]"],
|
||||
"importOrderSeparation": true,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.html",
|
||||
"options": {
|
||||
"parser": "angular"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.ts",
|
||||
"options": {
|
||||
|
30
CHANGELOG.md
30
CHANGELOG.md
@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 2.55.0 - 2024-02-22
|
||||
|
||||
### Added
|
||||
|
||||
- Added indexes for `alias`, `granteeUserId` and `userId` to the access database table
|
||||
- Added indexes for `currency`, `name` and `userId` to the account database table
|
||||
- Added an index for `accountId`, `date` and `updatedAt` to the account balance database table
|
||||
- Added an index for `userId` to the auth device database table
|
||||
- Added an index for `marketPrice` and `state` to the market data database table
|
||||
- Added indexes for `date`, `isDraft` and `userId` to the order database table
|
||||
- Added an index for `name` to the platform database table
|
||||
- Added indexes for `assetClass`, `currency`, `dataSource`, `isin`, `name` and `symbol` to the symbol profile database table
|
||||
- Added an index for `userId` to the subscription database table
|
||||
- Added an index for `name` to the tag database table
|
||||
- Added indexes for `accessToken`, `createdAt`, `provider`, `role` and `thirdPartyId` to the user database table
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved the validation for `currency` in various endpoints
|
||||
- Harmonized the setting of a default locale in various components
|
||||
- Set the parser to `angular` in the `prettier` options
|
||||
|
||||
## 2.54.0 - 2024-02-19
|
||||
|
||||
### Added
|
||||
|
||||
- Added an index for `id` to the account database table
|
||||
- Added indexes for `dataSource` and `date` to the market data database table
|
||||
- Added an index for `accountId` to the order database table
|
||||
|
||||
## 2.53.1 - 2024-02-18
|
||||
|
||||
### Added
|
||||
|
@ -21,10 +21,8 @@ export class AccountService {
|
||||
public async account({
|
||||
id_userId
|
||||
}: Prisma.AccountWhereUniqueInput): Promise<Account | null> {
|
||||
const { id, userId } = id_userId;
|
||||
|
||||
const [account] = await this.accounts({
|
||||
where: { id, userId }
|
||||
where: id_userId
|
||||
});
|
||||
|
||||
return account;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Transform, TransformFnParams } from 'class-transformer';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsISO4217CurrencyCode,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
@ -19,7 +20,7 @@ export class CreateAccountDto {
|
||||
)
|
||||
comment?: string;
|
||||
|
||||
@IsString()
|
||||
@IsISO4217CurrencyCode()
|
||||
currency: string;
|
||||
|
||||
@IsOptional()
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Transform, TransformFnParams } from 'class-transformer';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsISO4217CurrencyCode,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
@ -19,7 +20,7 @@ export class UpdateAccountDto {
|
||||
)
|
||||
comment?: string;
|
||||
|
||||
@IsString()
|
||||
@IsISO4217CurrencyCode()
|
||||
currency: string;
|
||||
|
||||
@IsString()
|
||||
|
@ -2,6 +2,7 @@ import { AssetClass, AssetSubClass, Prisma } from '@prisma/client';
|
||||
import {
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsISO4217CurrencyCode,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString
|
||||
@ -24,7 +25,7 @@ export class UpdateAssetProfileDto {
|
||||
@IsOptional()
|
||||
countries?: Prisma.InputJsonArray;
|
||||
|
||||
@IsString()
|
||||
@IsISO4217CurrencyCode()
|
||||
@IsOptional()
|
||||
currency?: string;
|
||||
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsISO4217CurrencyCode,
|
||||
IsISO8601,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
@ -38,7 +39,7 @@ export class CreateOrderDto {
|
||||
)
|
||||
comment?: string;
|
||||
|
||||
@IsString()
|
||||
@IsISO4217CurrencyCode()
|
||||
currency: string;
|
||||
|
||||
@IsOptional()
|
||||
|
@ -9,6 +9,7 @@ import { Transform, TransformFnParams } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsISO4217CurrencyCode,
|
||||
IsISO8601,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
@ -37,7 +38,7 @@ export class UpdateOrderDto {
|
||||
)
|
||||
comment?: string;
|
||||
|
||||
@IsString()
|
||||
@IsISO4217CurrencyCode()
|
||||
currency: string;
|
||||
|
||||
@IsString()
|
||||
|
@ -7,6 +7,7 @@ import type {
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsISO4217CurrencyCode,
|
||||
IsISO8601,
|
||||
IsIn,
|
||||
IsNumber,
|
||||
@ -19,8 +20,8 @@ export class UpdateUserSettingDto {
|
||||
@IsOptional()
|
||||
annualInterestRate?: number;
|
||||
|
||||
@IsISO4217CurrencyCode()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
baseCurrency?: string;
|
||||
|
||||
@IsString()
|
||||
|
@ -438,7 +438,7 @@ export class UserService {
|
||||
settings
|
||||
},
|
||||
where: {
|
||||
userId: userId
|
||||
userId
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -37,12 +37,14 @@ export class AlphaVantageService implements DataProviderInterface {
|
||||
return !!this.configurationService.get('API_KEY_ALPHA_VANTAGE');
|
||||
}
|
||||
|
||||
public async getAssetProfile(
|
||||
aSymbol: string
|
||||
): Promise<Partial<SymbolProfile>> {
|
||||
public async getAssetProfile({
|
||||
symbol
|
||||
}: {
|
||||
symbol: string;
|
||||
}): Promise<Partial<SymbolProfile>> {
|
||||
return {
|
||||
dataSource: this.getName(),
|
||||
symbol: aSymbol
|
||||
symbol,
|
||||
dataSource: this.getName()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -52,15 +52,17 @@ export class CoinGeckoService implements DataProviderInterface {
|
||||
return true;
|
||||
}
|
||||
|
||||
public async getAssetProfile(
|
||||
aSymbol: string
|
||||
): Promise<Partial<SymbolProfile>> {
|
||||
public async getAssetProfile({
|
||||
symbol
|
||||
}: {
|
||||
symbol: string;
|
||||
}): Promise<Partial<SymbolProfile>> {
|
||||
const response: Partial<SymbolProfile> = {
|
||||
symbol,
|
||||
assetClass: AssetClass.CASH,
|
||||
assetSubClass: AssetSubClass.CRYPTOCURRENCY,
|
||||
currency: DEFAULT_CURRENCY,
|
||||
dataSource: this.getName(),
|
||||
symbol: aSymbol
|
||||
dataSource: this.getName()
|
||||
};
|
||||
|
||||
try {
|
||||
@ -70,7 +72,7 @@ export class CoinGeckoService implements DataProviderInterface {
|
||||
abortController.abort();
|
||||
}, this.configurationService.get('REQUEST_TIMEOUT'));
|
||||
|
||||
const { name } = await got(`${this.apiUrl}/coins/${aSymbol}`, {
|
||||
const { name } = await got(`${this.apiUrl}/coins/${symbol}`, {
|
||||
headers: this.headers,
|
||||
// @ts-ignore
|
||||
signal: abortController.signal
|
||||
@ -81,7 +83,7 @@ export class CoinGeckoService implements DataProviderInterface {
|
||||
let message = error;
|
||||
|
||||
if (error?.code === 'ABORT_ERR') {
|
||||
message = `RequestError: The operation to get the asset profile for ${aSymbol} was aborted because the request to the data provider took more than ${this.configurationService.get(
|
||||
message = `RequestError: The operation to get the asset profile for ${symbol} was aborted because the request to the data provider took more than ${this.configurationService.get(
|
||||
'REQUEST_TIMEOUT'
|
||||
)}ms`;
|
||||
}
|
||||
|
@ -92,7 +92,9 @@ export class DataProviderService {
|
||||
|
||||
for (const symbol of symbols) {
|
||||
const promise = Promise.resolve(
|
||||
this.getDataProvider(DataSource[dataSource]).getAssetProfile(symbol)
|
||||
this.getDataProvider(DataSource[dataSource]).getAssetProfile({
|
||||
symbol
|
||||
})
|
||||
);
|
||||
|
||||
promises.push(
|
||||
|
@ -46,19 +46,21 @@ export class EodHistoricalDataService implements DataProviderInterface {
|
||||
return true;
|
||||
}
|
||||
|
||||
public async getAssetProfile(
|
||||
aSymbol: string
|
||||
): Promise<Partial<SymbolProfile>> {
|
||||
const [searchResult] = await this.getSearchResult(aSymbol);
|
||||
public async getAssetProfile({
|
||||
symbol
|
||||
}: {
|
||||
symbol: string;
|
||||
}): Promise<Partial<SymbolProfile>> {
|
||||
const [searchResult] = await this.getSearchResult(symbol);
|
||||
|
||||
return {
|
||||
symbol,
|
||||
assetClass: searchResult?.assetClass,
|
||||
assetSubClass: searchResult?.assetSubClass,
|
||||
currency: this.convertCurrency(searchResult?.currency),
|
||||
dataSource: this.getName(),
|
||||
isin: searchResult?.isin,
|
||||
name: searchResult?.name,
|
||||
symbol: aSymbol
|
||||
name: searchResult?.name
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -37,12 +37,14 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
||||
return true;
|
||||
}
|
||||
|
||||
public async getAssetProfile(
|
||||
aSymbol: string
|
||||
): Promise<Partial<SymbolProfile>> {
|
||||
public async getAssetProfile({
|
||||
symbol
|
||||
}: {
|
||||
symbol: string;
|
||||
}): Promise<Partial<SymbolProfile>> {
|
||||
return {
|
||||
dataSource: this.getName(),
|
||||
symbol: aSymbol
|
||||
symbol,
|
||||
dataSource: this.getName()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -33,12 +33,14 @@ export class GoogleSheetsService implements DataProviderInterface {
|
||||
return true;
|
||||
}
|
||||
|
||||
public async getAssetProfile(
|
||||
aSymbol: string
|
||||
): Promise<Partial<SymbolProfile>> {
|
||||
public async getAssetProfile({
|
||||
symbol
|
||||
}: {
|
||||
symbol: string;
|
||||
}): Promise<Partial<SymbolProfile>> {
|
||||
return {
|
||||
dataSource: this.getName(),
|
||||
symbol: aSymbol
|
||||
symbol,
|
||||
dataSource: this.getName()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,11 @@ import { DataSource, SymbolProfile } from '@prisma/client';
|
||||
export interface DataProviderInterface {
|
||||
canHandle(symbol: string): boolean;
|
||||
|
||||
getAssetProfile(aSymbol: string): Promise<Partial<SymbolProfile>>;
|
||||
getAssetProfile({
|
||||
symbol
|
||||
}: {
|
||||
symbol: string;
|
||||
}): Promise<Partial<SymbolProfile>>;
|
||||
|
||||
getDataProviderInfo(): DataProviderInfo;
|
||||
|
||||
|
@ -43,16 +43,18 @@ export class ManualService implements DataProviderInterface {
|
||||
return true;
|
||||
}
|
||||
|
||||
public async getAssetProfile(
|
||||
aSymbol: string
|
||||
): Promise<Partial<SymbolProfile>> {
|
||||
public async getAssetProfile({
|
||||
symbol
|
||||
}: {
|
||||
symbol: string;
|
||||
}): Promise<Partial<SymbolProfile>> {
|
||||
const assetProfile: Partial<SymbolProfile> = {
|
||||
dataSource: this.getName(),
|
||||
symbol: aSymbol
|
||||
symbol,
|
||||
dataSource: this.getName()
|
||||
};
|
||||
|
||||
const [symbolProfile] = await this.symbolProfileService.getSymbolProfiles([
|
||||
{ dataSource: this.getName(), symbol: aSymbol }
|
||||
{ symbol, dataSource: this.getName() }
|
||||
]);
|
||||
|
||||
if (symbolProfile) {
|
||||
|
@ -30,12 +30,14 @@ export class RapidApiService implements DataProviderInterface {
|
||||
return !!this.configurationService.get('API_KEY_RAPID_API');
|
||||
}
|
||||
|
||||
public async getAssetProfile(
|
||||
aSymbol: string
|
||||
): Promise<Partial<SymbolProfile>> {
|
||||
public async getAssetProfile({
|
||||
symbol
|
||||
}: {
|
||||
symbol: string;
|
||||
}): Promise<Partial<SymbolProfile>> {
|
||||
return {
|
||||
dataSource: this.getName(),
|
||||
symbol: aSymbol
|
||||
symbol,
|
||||
dataSource: this.getName()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -33,11 +33,13 @@ export class YahooFinanceService implements DataProviderInterface {
|
||||
return true;
|
||||
}
|
||||
|
||||
public async getAssetProfile(
|
||||
aSymbol: string
|
||||
): Promise<Partial<SymbolProfile>> {
|
||||
const { assetClass, assetSubClass, currency, name, symbol } =
|
||||
await this.yahooFinanceDataEnhancerService.getAssetProfile(aSymbol);
|
||||
public async getAssetProfile({
|
||||
symbol
|
||||
}: {
|
||||
symbol: string;
|
||||
}): Promise<Partial<SymbolProfile>> {
|
||||
const { assetClass, assetSubClass, currency, name } =
|
||||
await this.yahooFinanceDataEnhancerService.getAssetProfile(symbol);
|
||||
|
||||
return {
|
||||
assetClass,
|
||||
|
@ -25,7 +25,9 @@
|
||||
class="h-100"
|
||||
[currency]="user?.settings?.baseCurrency"
|
||||
[historicalDataItems]="historicalDataItems"
|
||||
[isInPercent]="data.hasImpersonationId || user.settings.isRestrictedView"
|
||||
[isInPercent]="
|
||||
data.hasImpersonationId || user.settings.isRestrictedView
|
||||
"
|
||||
[isLoading]="isLoadingChart"
|
||||
[locale]="user?.settings?.locale"
|
||||
/>
|
||||
@ -92,7 +94,9 @@
|
||||
[dataSource]="dataSource"
|
||||
[deviceType]="data.deviceType"
|
||||
[hasPermissionToCreateActivity]="false"
|
||||
[hasPermissionToExportActivities]="!data.hasImpersonationId && !user.settings.isRestrictedView"
|
||||
[hasPermissionToExportActivities]="
|
||||
!data.hasImpersonationId && !user.settings.isRestrictedView
|
||||
"
|
||||
[hasPermissionToFilter]="false"
|
||||
[hasPermissionToOpenDetails]="false"
|
||||
[locale]="user?.settings?.locale"
|
||||
@ -113,7 +117,11 @@
|
||||
[accountBalances]="accountBalances"
|
||||
[accountId]="data.accountId"
|
||||
[locale]="user?.settings?.locale"
|
||||
[showActions]="!data.hasImpersonationId && hasPermissionToDeleteAccountBalance && !user.settings.isRestrictedView"
|
||||
[showActions]="
|
||||
!data.hasImpersonationId &&
|
||||
hasPermissionToDeleteAccountBalance &&
|
||||
!user.settings.isRestrictedView
|
||||
"
|
||||
(accountBalanceDeleted)="onDeleteAccountBalance($event)"
|
||||
/>
|
||||
</mat-tab>
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { getLocale } from '@ghostfolio/common/helper';
|
||||
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
@ -27,7 +29,7 @@ export class AccountsTableComponent implements OnChanges, OnDestroy, OnInit {
|
||||
@Input() baseCurrency: string;
|
||||
@Input() deviceType: string;
|
||||
@Input() hasPermissionToOpenDetails = true;
|
||||
@Input() locale: string;
|
||||
@Input() locale = getLocale();
|
||||
@Input() showActions: boolean;
|
||||
@Input() showBalance = true;
|
||||
@Input() showFooter = true;
|
||||
|
@ -163,7 +163,12 @@
|
||||
<mat-menu #assetProfileActionsMenu="matMenu" xPosition="before">
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="onOpenAssetProfileDialog({ dataSource: element.dataSource, symbol: element.symbol })"
|
||||
(click)="
|
||||
onOpenAssetProfileDialog({
|
||||
dataSource: element.dataSource,
|
||||
symbol: element.symbol
|
||||
})
|
||||
"
|
||||
>
|
||||
<span class="align-items-center d-flex">
|
||||
<ion-icon class="mr-2" name="create-outline" />
|
||||
@ -173,7 +178,12 @@
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="element.activitiesCount !== 0"
|
||||
(click)="onDeleteProfileData({dataSource: element.dataSource, symbol: element.symbol})"
|
||||
(click)="
|
||||
onDeleteProfileData({
|
||||
dataSource: element.dataSource,
|
||||
symbol: element.symbol
|
||||
})
|
||||
"
|
||||
>
|
||||
<span class="align-items-center d-flex">
|
||||
<ion-icon class="mr-2" name="trash-outline" />
|
||||
@ -189,16 +199,19 @@
|
||||
*matRowDef="let row; columns: displayedColumns"
|
||||
class="cursor-pointer"
|
||||
mat-row
|
||||
(click)="onOpenAssetProfileDialog({ dataSource: row.dataSource, symbol: row.symbol })"
|
||||
(click)="
|
||||
onOpenAssetProfileDialog({
|
||||
dataSource: row.dataSource,
|
||||
symbol: row.symbol
|
||||
})
|
||||
"
|
||||
></tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator
|
||||
[length]="totalItems"
|
||||
[ngClass]="{
|
||||
'd-none':
|
||||
(isLoading && totalItems === 0) ||
|
||||
totalItems <= pageSize
|
||||
'd-none': (isLoading && totalItems === 0) || totalItems <= pageSize
|
||||
}"
|
||||
[pageSize]="pageSize"
|
||||
[showFirstLastButtons]="true"
|
||||
|
@ -25,7 +25,9 @@
|
||||
mat-menu-item
|
||||
type="button"
|
||||
[disabled]="assetProfileForm.dirty"
|
||||
(click)="onGatherSymbol({dataSource: data.dataSource, symbol: data.symbol})"
|
||||
(click)="
|
||||
onGatherSymbol({ dataSource: data.dataSource, symbol: data.symbol })
|
||||
"
|
||||
>
|
||||
<ng-container i18n>Gather Historical Data</ng-container>
|
||||
</button>
|
||||
@ -33,7 +35,12 @@
|
||||
mat-menu-item
|
||||
type="button"
|
||||
[disabled]="assetProfileForm.dirty"
|
||||
(click)="onGatherProfileDataBySymbol({dataSource: data.dataSource, symbol: data.symbol})"
|
||||
(click)="
|
||||
onGatherProfileDataBySymbol({
|
||||
dataSource: data.dataSource,
|
||||
symbol: data.symbol
|
||||
})
|
||||
"
|
||||
>
|
||||
<ng-container i18n>Gather Profile Data</ng-container>
|
||||
</button>
|
||||
@ -73,7 +80,12 @@
|
||||
color="accent"
|
||||
mat-flat-button
|
||||
type="button"
|
||||
[disabled]="!assetProfileForm.controls['historicalData']?.controls['csvString'].touched || assetProfileForm.controls['historicalData']?.controls['csvString']?.value === ''"
|
||||
[disabled]="
|
||||
!assetProfileForm.controls['historicalData']?.controls['csvString']
|
||||
.touched ||
|
||||
assetProfileForm.controls['historicalData']?.controls['csvString']
|
||||
?.value === ''
|
||||
"
|
||||
(click)="onImportHistoricalData()"
|
||||
>
|
||||
<ng-container i18n>Import</ng-container>
|
||||
@ -129,49 +141,54 @@
|
||||
>
|
||||
</div>
|
||||
<ng-container
|
||||
*ngIf="assetProfile?.countries?.length > 0 || assetProfile?.sectors?.length > 0"
|
||||
*ngIf="
|
||||
assetProfile?.countries?.length > 0 ||
|
||||
assetProfile?.sectors?.length > 0
|
||||
"
|
||||
>
|
||||
@if (assetProfile?.countries?.length === 1 &&
|
||||
assetProfile?.sectors?.length === 1 ) {
|
||||
<div *ngIf="assetProfile?.sectors?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="assetProfile?.sectors[0].name"
|
||||
>Sector</gf-value
|
||||
>
|
||||
</div>
|
||||
<div *ngIf="assetProfile?.countries?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="assetProfile?.countries[0].name"
|
||||
>Country</gf-value
|
||||
>
|
||||
</div>
|
||||
@if (
|
||||
assetProfile?.countries?.length === 1 &&
|
||||
assetProfile?.sectors?.length === 1
|
||||
) {
|
||||
<div *ngIf="assetProfile?.sectors?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="assetProfile?.sectors[0].name"
|
||||
>Sector</gf-value
|
||||
>
|
||||
</div>
|
||||
<div *ngIf="assetProfile?.countries?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="assetProfile?.countries[0].name"
|
||||
>Country</gf-value
|
||||
>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Sectors</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[maxItems]="10"
|
||||
[positions]="sectors"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Countries</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[maxItems]="10"
|
||||
[positions]="countries"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Sectors</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[maxItems]="10"
|
||||
[positions]="sectors"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Countries</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[maxItems]="10"
|
||||
[positions]="countries"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
</div>
|
||||
@ -222,7 +239,17 @@
|
||||
color="primary"
|
||||
i18n
|
||||
[checked]="isBenchmark"
|
||||
(change)="isBenchmark ? onUnsetBenchmark({dataSource: data.dataSource, symbol: data.symbol}) : onSetBenchmark({dataSource: data.dataSource, symbol: data.symbol})"
|
||||
(change)="
|
||||
isBenchmark
|
||||
? onUnsetBenchmark({
|
||||
dataSource: data.dataSource,
|
||||
symbol: data.symbol
|
||||
})
|
||||
: onSetBenchmark({
|
||||
dataSource: data.dataSource,
|
||||
symbol: data.symbol
|
||||
})
|
||||
"
|
||||
>Benchmark</mat-checkbox
|
||||
>
|
||||
</div>
|
||||
@ -253,7 +280,9 @@
|
||||
color="accent"
|
||||
mat-flat-button
|
||||
type="button"
|
||||
[disabled]="assetProfileForm.controls['scraperConfiguration'].value === '{}'"
|
||||
[disabled]="
|
||||
assetProfileForm.controls['scraperConfiguration'].value === '{}'
|
||||
"
|
||||
(click)="onTestMarketData()"
|
||||
>
|
||||
<ng-container i18n>Test</ng-container>
|
||||
|
@ -28,7 +28,7 @@
|
||||
[value]="transactionCount"
|
||||
/>
|
||||
<div *ngIf="transactionCount && userCount">
|
||||
{{ transactionCount / userCount | number : '1.2-2' }}
|
||||
{{ transactionCount / userCount | number: '1.2-2' }}
|
||||
<span i18n>per User</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -69,10 +69,10 @@
|
||||
<a
|
||||
mat-menu-item
|
||||
[queryParams]="{
|
||||
assetProfileDialog: true,
|
||||
dataSource: exchangeRate.dataSource,
|
||||
symbol: exchangeRate.symbol
|
||||
}"
|
||||
assetProfileDialog: true,
|
||||
dataSource: exchangeRate.dataSource,
|
||||
symbol: exchangeRate.symbol
|
||||
}"
|
||||
[routerLink]="['/admin', 'market-data']"
|
||||
>
|
||||
<span class="align-items-center d-flex">
|
||||
@ -112,7 +112,9 @@
|
||||
<mat-slide-toggle
|
||||
color="primary"
|
||||
hideIcon="true"
|
||||
[checked]="info.globalPermissions.includes(permissions.createUserAccount)"
|
||||
[checked]="
|
||||
info.globalPermissions.includes(permissions.createUserAccount)
|
||||
"
|
||||
(change)="onEnableUserSignupModeChange($event)"
|
||||
/>
|
||||
</div>
|
||||
@ -143,7 +145,7 @@
|
||||
<div class="w-50" i18n>System Message</div>
|
||||
<div class="w-50">
|
||||
<div *ngIf="systemMessage" class="align-items-center d-flex">
|
||||
<div class="text-truncate">{{ systemMessage | json }}</div>
|
||||
<div class="text-truncate">{{ systemMessage | json }}</div>
|
||||
<button
|
||||
class="h-100 mx-1 no-min-width px-2"
|
||||
mat-button
|
||||
|
@ -12,7 +12,7 @@
|
||||
#
|
||||
</th>
|
||||
<td
|
||||
*matCellDef="let element; let i=index"
|
||||
*matCellDef="let element; let i = index"
|
||||
class="mat-mdc-cell px-1 py-2 text-right"
|
||||
mat-cell
|
||||
>
|
||||
@ -35,17 +35,23 @@
|
||||
mat-cell
|
||||
>
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="d-none d-sm-inline-block text-monospace"
|
||||
>{{ element.id }}</span
|
||||
>
|
||||
<span class="d-inline-block d-sm-none text-monospace"
|
||||
>{{ (element.id | slice:0:5) + '...' }}</span
|
||||
>
|
||||
<span class="d-none d-sm-inline-block text-monospace">{{
|
||||
element.id
|
||||
}}</span>
|
||||
<span class="d-inline-block d-sm-none text-monospace">{{
|
||||
(element.id | slice: 0 : 5) + '...'
|
||||
}}</span>
|
||||
<gf-premium-indicator
|
||||
*ngIf="element?.subscription?.type === 'Premium'"
|
||||
class="ml-1"
|
||||
[enableLink]="false"
|
||||
[title]="'Expires ' + formatDistanceToNow(element.subscription.expiresAt) + ' (' + (element.subscription.expiresAt | date: defaultDateFormat) + ')'"
|
||||
[title]="
|
||||
'Expires ' +
|
||||
formatDistanceToNow(element.subscription.expiresAt) +
|
||||
' (' +
|
||||
(element.subscription.expiresAt | date: defaultDateFormat) +
|
||||
')'
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
@ -67,9 +73,9 @@
|
||||
class="mat-mdc-cell px-1 py-2"
|
||||
mat-cell
|
||||
>
|
||||
<span class="h5" [title]="element.country"
|
||||
>{{ getEmojiFlag(element.country) }}</span
|
||||
>
|
||||
<span class="h5" [title]="element.country">{{
|
||||
getEmojiFlag(element.country)
|
||||
}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
@ -7,6 +7,7 @@ import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
|
||||
import {
|
||||
getBackgroundColor,
|
||||
getDateFormatString,
|
||||
getLocale,
|
||||
getTextColor,
|
||||
parseDate
|
||||
} from '@ghostfolio/common/helper';
|
||||
@ -51,7 +52,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
||||
@Input() colorScheme: ColorScheme;
|
||||
@Input() daysInMarket: number;
|
||||
@Input() isLoading: boolean;
|
||||
@Input() locale: string;
|
||||
@Input() locale = getLocale();
|
||||
@Input() performanceDataItems: LineChartItem[];
|
||||
@Input() user: User;
|
||||
|
||||
|
@ -1,100 +1,100 @@
|
||||
<div
|
||||
class="align-items-center container d-flex flex-column h-100 justify-content-center overview p-0 position-relative"
|
||||
>
|
||||
@if(hasPermissionToCreateOrder && historicalDataItems?.length === 0) {
|
||||
<div class="justify-content-center row w-100">
|
||||
<div class="col introduction">
|
||||
<h4 i18n>Welcome to Ghostfolio</h4>
|
||||
<p i18n>Ready to take control of your personal finances?</p>
|
||||
<ol class="font-weight-bold">
|
||||
<li
|
||||
class="mb-2"
|
||||
[ngClass]="{ 'text-muted': user?.accounts?.length > 1 }"
|
||||
>
|
||||
<a class="d-block" [routerLink]="['/accounts']"
|
||||
><span i18n>Setup your accounts</span><br />
|
||||
<span class="font-weight-normal" i18n
|
||||
>Get a comprehensive financial overview by adding your bank and
|
||||
brokerage accounts.</span
|
||||
></a
|
||||
@if (hasPermissionToCreateOrder && historicalDataItems?.length === 0) {
|
||||
<div class="justify-content-center row w-100">
|
||||
<div class="col introduction">
|
||||
<h4 i18n>Welcome to Ghostfolio</h4>
|
||||
<p i18n>Ready to take control of your personal finances?</p>
|
||||
<ol class="font-weight-bold">
|
||||
<li
|
||||
class="mb-2"
|
||||
[ngClass]="{ 'text-muted': user?.accounts?.length > 1 }"
|
||||
>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<a class="d-block" [routerLink]="['/portfolio', 'activities']">
|
||||
<span i18n>Capture your activities</span><br />
|
||||
<span class="font-weight-normal" i18n
|
||||
>Record your investment activities to keep your portfolio up to
|
||||
date.</span
|
||||
></a
|
||||
>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<a class="d-block" [routerLink]="['/portfolio']">
|
||||
<span i18n>Monitor and analyze your portfolio</span><br />
|
||||
<span class="font-weight-normal" i18n
|
||||
>Track your progress in real-time with comprehensive analysis and
|
||||
insights.</span
|
||||
<a class="d-block" [routerLink]="['/accounts']"
|
||||
><span i18n>Setup your accounts</span><br />
|
||||
<span class="font-weight-normal" i18n
|
||||
>Get a comprehensive financial overview by adding your bank and
|
||||
brokerage accounts.</span
|
||||
></a
|
||||
>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<a class="d-block" [routerLink]="['/portfolio', 'activities']">
|
||||
<span i18n>Capture your activities</span><br />
|
||||
<span class="font-weight-normal" i18n
|
||||
>Record your investment activities to keep your portfolio up to
|
||||
date.</span
|
||||
></a
|
||||
>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<a class="d-block" [routerLink]="['/portfolio']">
|
||||
<span i18n>Monitor and analyze your portfolio</span><br />
|
||||
<span class="font-weight-normal" i18n
|
||||
>Track your progress in real-time with comprehensive analysis
|
||||
and insights.</span
|
||||
>
|
||||
</a>
|
||||
</li>
|
||||
</ol>
|
||||
<div class="d-flex justify-content-center">
|
||||
<a
|
||||
*ngIf="user?.accounts?.length === 1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[routerLink]="['/accounts']"
|
||||
>
|
||||
<ng-container i18n>Setup accounts</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
</ol>
|
||||
<div class="d-flex justify-content-center">
|
||||
<a
|
||||
*ngIf="user?.accounts?.length === 1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[routerLink]="['/accounts']"
|
||||
>
|
||||
<ng-container i18n>Setup accounts</ng-container>
|
||||
</a>
|
||||
<a
|
||||
*ngIf="user?.accounts?.length > 1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[routerLink]="['/portfolio', 'activities']"
|
||||
>
|
||||
<ng-container i18n>Add activity</ng-container>
|
||||
</a>
|
||||
<a
|
||||
*ngIf="user?.accounts?.length > 1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[routerLink]="['/portfolio', 'activities']"
|
||||
>
|
||||
<ng-container i18n>Add activity</ng-container>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="row w-100">
|
||||
<div class="col p-0">
|
||||
<div class="chart-container mx-auto position-relative">
|
||||
<gf-line-chart
|
||||
class="position-absolute"
|
||||
symbol="Performance"
|
||||
unit="%"
|
||||
[colorScheme]="user?.settings?.colorScheme"
|
||||
[hidden]="historicalDataItems?.length === 0"
|
||||
[historicalDataItems]="historicalDataItems"
|
||||
[isAnimated]="user?.settings?.dateRange === '1d' ? false : true"
|
||||
<div class="row w-100">
|
||||
<div class="col p-0">
|
||||
<div class="chart-container mx-auto position-relative">
|
||||
<gf-line-chart
|
||||
class="position-absolute"
|
||||
symbol="Performance"
|
||||
unit="%"
|
||||
[colorScheme]="user?.settings?.colorScheme"
|
||||
[hidden]="historicalDataItems?.length === 0"
|
||||
[historicalDataItems]="historicalDataItems"
|
||||
[isAnimated]="user?.settings?.dateRange === '1d' ? false : true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[ngClass]="{ 'pr-3': deviceType === 'mobile' }"
|
||||
[showGradient]="true"
|
||||
[showLoader]="false"
|
||||
[showXAxis]="false"
|
||||
[showYAxis]="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overview-container row mt-1">
|
||||
<div class="col">
|
||||
<gf-portfolio-performance
|
||||
class="pb-4"
|
||||
[deviceType]="deviceType"
|
||||
[errors]="errors"
|
||||
[isAllTimeHigh]="isAllTimeHigh"
|
||||
[isAllTimeLow]="isAllTimeLow"
|
||||
[isLoading]="isLoadingPerformance"
|
||||
[locale]="user?.settings?.locale"
|
||||
[ngClass]="{ 'pr-3': deviceType === 'mobile' }"
|
||||
[showGradient]="true"
|
||||
[showLoader]="false"
|
||||
[showXAxis]="false"
|
||||
[showYAxis]="false"
|
||||
[performance]="performance"
|
||||
[showDetails]="showDetails"
|
||||
[unit]="unit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overview-container row mt-1">
|
||||
<div class="col">
|
||||
<gf-portfolio-performance
|
||||
class="pb-4"
|
||||
[deviceType]="deviceType"
|
||||
[errors]="errors"
|
||||
[isAllTimeHigh]="isAllTimeHigh"
|
||||
[isAllTimeLow]="isAllTimeLow"
|
||||
[isLoading]="isLoadingPerformance"
|
||||
[locale]="user?.settings?.locale"
|
||||
[performance]="performance"
|
||||
[showDetails]="showDetails"
|
||||
[unit]="unit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -6,7 +6,9 @@
|
||||
<mat-card-content>
|
||||
<gf-portfolio-summary
|
||||
[baseCurrency]="user?.settings?.baseCurrency"
|
||||
[hasPermissionToUpdateUserSettings]="!hasImpersonationId && hasPermissionToUpdateUserSettings"
|
||||
[hasPermissionToUpdateUserSettings]="
|
||||
!hasImpersonationId && hasPermissionToUpdateUserSettings
|
||||
"
|
||||
[isLoading]="isLoading"
|
||||
[language]="user?.settings?.language"
|
||||
[locale]="user?.settings?.locale"
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
DATE_FORMAT,
|
||||
getBackgroundColor,
|
||||
getDateFormatString,
|
||||
getLocale,
|
||||
getTextColor,
|
||||
parseDate
|
||||
} from '@ghostfolio/common/helper';
|
||||
@ -65,7 +66,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
|
||||
@Input() historicalDataItems: LineChartItem[] = [];
|
||||
@Input() isInPercent = false;
|
||||
@Input() isLoading = false;
|
||||
@Input() locale: string;
|
||||
@Input() locale = getLocale();
|
||||
@Input() range: DateRange = 'max';
|
||||
@Input() savingsRate = 0;
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
getLocale,
|
||||
getNumberFormatDecimal,
|
||||
getNumberFormatGroup
|
||||
} from '@ghostfolio/common/helper';
|
||||
@ -31,7 +32,7 @@ export class PortfolioPerformanceComponent implements OnChanges, OnInit {
|
||||
@Input() isAllTimeHigh: boolean;
|
||||
@Input() isAllTimeLow: boolean;
|
||||
@Input() isLoading: boolean;
|
||||
@Input() locale: string;
|
||||
@Input() locale = getLocale();
|
||||
@Input() performance: PortfolioPerformance;
|
||||
@Input() showDetails: boolean;
|
||||
@Input() unit: string;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getDateFnsLocale } from '@ghostfolio/common/helper';
|
||||
import { getDateFnsLocale, getLocale } from '@ghostfolio/common/helper';
|
||||
import { PortfolioSummary } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import {
|
||||
@ -23,7 +23,7 @@ export class PortfolioSummaryComponent implements OnChanges, OnInit {
|
||||
@Input() hasPermissionToUpdateUserSettings: boolean;
|
||||
@Input() isLoading: boolean;
|
||||
@Input() language: string;
|
||||
@Input() locale: string;
|
||||
@Input() locale = getLocale();
|
||||
@Input() summary: PortfolioSummary;
|
||||
|
||||
@Output() emergencyFundChanged = new EventEmitter<number>();
|
||||
|
@ -87,7 +87,11 @@
|
||||
size="medium"
|
||||
[isCurrency]="true"
|
||||
[locale]="data.locale"
|
||||
[ngClass]="{ 'text-danger': minPrice?.toFixed(2) === marketPrice?.toFixed(2) && maxPrice?.toFixed(2) !== minPrice?.toFixed(2) }"
|
||||
[ngClass]="{
|
||||
'text-danger':
|
||||
minPrice?.toFixed(2) === marketPrice?.toFixed(2) &&
|
||||
maxPrice?.toFixed(2) !== minPrice?.toFixed(2)
|
||||
}"
|
||||
[unit]="SymbolProfile?.currency"
|
||||
[value]="minPrice"
|
||||
>Minimum Price</gf-value
|
||||
@ -99,7 +103,11 @@
|
||||
size="medium"
|
||||
[isCurrency]="true"
|
||||
[locale]="data.locale"
|
||||
[ngClass]="{ 'text-success': maxPrice?.toFixed(2) === marketPrice?.toFixed(2) && maxPrice?.toFixed(2) !== minPrice?.toFixed(2) }"
|
||||
[ngClass]="{
|
||||
'text-success':
|
||||
maxPrice?.toFixed(2) === marketPrice?.toFixed(2) &&
|
||||
maxPrice?.toFixed(2) !== minPrice?.toFixed(2)
|
||||
}"
|
||||
[unit]="SymbolProfile?.currency"
|
||||
[value]="maxPrice"
|
||||
>Maximum Price</gf-value
|
||||
@ -184,53 +192,61 @@
|
||||
>
|
||||
</div>
|
||||
<ng-container
|
||||
*ngIf="SymbolProfile?.countries?.length > 0 || SymbolProfile?.sectors?.length > 0"
|
||||
*ngIf="
|
||||
SymbolProfile?.countries?.length > 0 ||
|
||||
SymbolProfile?.sectors?.length > 0
|
||||
"
|
||||
>
|
||||
@if(SymbolProfile?.countries?.length === 1 &&
|
||||
SymbolProfile?.sectors?.length === 1) {
|
||||
<div *ngIf="SymbolProfile?.sectors?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="SymbolProfile.sectors[0].name"
|
||||
>Sector</gf-value
|
||||
@if (
|
||||
SymbolProfile?.countries?.length === 1 &&
|
||||
SymbolProfile?.sectors?.length === 1
|
||||
) {
|
||||
<div *ngIf="SymbolProfile?.sectors?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="SymbolProfile.sectors[0].name"
|
||||
>Sector</gf-value
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="SymbolProfile?.countries?.length === 1"
|
||||
class="col-6 mb-3"
|
||||
>
|
||||
</div>
|
||||
<div *ngIf="SymbolProfile?.countries?.length === 1" class="col-6 mb-3">
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="SymbolProfile.countries[0].name"
|
||||
>Country</gf-value
|
||||
>
|
||||
</div>
|
||||
<gf-value
|
||||
i18n
|
||||
size="medium"
|
||||
[locale]="data.locale"
|
||||
[value]="SymbolProfile.countries[0].name"
|
||||
>Country</gf-value
|
||||
>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Sectors</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[baseCurrency]="data.baseCurrency"
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[locale]="data.locale"
|
||||
[maxItems]="10"
|
||||
[positions]="sectors"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Countries</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[baseCurrency]="data.baseCurrency"
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[locale]="data.locale"
|
||||
[maxItems]="10"
|
||||
[positions]="countries"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Sectors</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[baseCurrency]="data.baseCurrency"
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[locale]="data.locale"
|
||||
[maxItems]="10"
|
||||
[positions]="sectors"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="h5" i18n>Countries</div>
|
||||
<gf-portfolio-proportion-chart
|
||||
[baseCurrency]="data.baseCurrency"
|
||||
[colorScheme]="data.colorScheme"
|
||||
[isInPercent]="true"
|
||||
[keys]="['name']"
|
||||
[locale]="data.locale"
|
||||
[maxItems]="10"
|
||||
[positions]="countries"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</ng-container>
|
||||
<div *ngIf="dataProviderInfo" class="col-md-12 mb-3 text-center">
|
||||
@ -257,7 +273,9 @@
|
||||
[dataSource]="dataSource"
|
||||
[deviceType]="data.deviceType"
|
||||
[hasPermissionToCreateActivity]="false"
|
||||
[hasPermissionToExportActivities]="!data.hasImpersonationId && !user.settings.isRestrictedView"
|
||||
[hasPermissionToExportActivities]="
|
||||
!data.hasImpersonationId && !user.settings.isRestrictedView
|
||||
"
|
||||
[hasPermissionToFilter]="false"
|
||||
[hasPermissionToOpenDetails]="false"
|
||||
[locale]="data.locale"
|
||||
@ -294,15 +312,17 @@
|
||||
<div class="col">
|
||||
<div class="h5" i18n>Tags</div>
|
||||
<mat-chip-listbox>
|
||||
<mat-chip-option *ngFor="let tag of tags" disabled
|
||||
>{{ tag.name }}</mat-chip-option
|
||||
>
|
||||
<mat-chip-option *ngFor="let tag of tags" disabled>{{
|
||||
tag.name
|
||||
}}</mat-chip-option>
|
||||
</mat-chip-listbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="activities?.length > 0 && data.hasPermissionToReportDataGlitch === true"
|
||||
*ngIf="
|
||||
activities?.length > 0 && data.hasPermissionToReportDataGlitch === true
|
||||
"
|
||||
class="row"
|
||||
>
|
||||
<div class="col">
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
||||
import { getLocale } from '@ghostfolio/common/helper';
|
||||
import { Position } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import {
|
||||
@ -20,7 +21,7 @@ export class PositionComponent implements OnDestroy, OnInit {
|
||||
@Input() baseCurrency: string;
|
||||
@Input() deviceType: string;
|
||||
@Input() isLoading: boolean;
|
||||
@Input() locale: string;
|
||||
@Input() locale = getLocale();
|
||||
@Input() position: Position;
|
||||
@Input() range: string;
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { getLocale } from '@ghostfolio/common/helper';
|
||||
import { Position } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import {
|
||||
@ -18,7 +19,7 @@ export class PositionsComponent implements OnChanges, OnInit {
|
||||
@Input() baseCurrency: string;
|
||||
@Input() deviceType: string;
|
||||
@Input() hasPermissionToCreateOrder: boolean;
|
||||
@Input() locale: string;
|
||||
@Input() locale = getLocale();
|
||||
@Input() positions: Position[];
|
||||
@Input() range: string;
|
||||
|
||||
|
@ -28,30 +28,32 @@
|
||||
</div>
|
||||
|
||||
@if (accessForm.controls['type'].value === 'PRIVATE') {
|
||||
<div>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Permission</mat-label>
|
||||
<mat-select formControlName="permissions">
|
||||
<mat-option i18n value="READ_RESTRICTED">Restricted view</mat-option>
|
||||
@if(data?.user?.settings?.isExperimentalFeatures) {
|
||||
<mat-option i18n value="READ">View</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label>
|
||||
Ghostfolio <ng-container i18n>User ID</ng-container>
|
||||
</mat-label>
|
||||
<input
|
||||
formControlName="userId"
|
||||
matInput
|
||||
type="text"
|
||||
(keydown.enter)="$event.stopPropagation()"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Permission</mat-label>
|
||||
<mat-select formControlName="permissions">
|
||||
<mat-option i18n value="READ_RESTRICTED"
|
||||
>Restricted view</mat-option
|
||||
>
|
||||
@if (data?.user?.settings?.isExperimentalFeatures) {
|
||||
<mat-option i18n value="READ">View</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label>
|
||||
Ghostfolio <ng-container i18n>User ID</ng-container>
|
||||
</mat-label>
|
||||
<input
|
||||
formControlName="userId"
|
||||
matInput
|
||||
type="text"
|
||||
(keydown.enter)="$event.stopPropagation()"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="justify-content-end" mat-dialog-actions>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="col">
|
||||
<div class="align-items-center d-flex flex-column">
|
||||
<gf-membership-card
|
||||
[expiresAt]="user?.subscription?.expiresAt | date: defaultDateFormat"
|
||||
[expiresAt]="user?.subscription?.expiresAt | date: defaultDateFormat"
|
||||
[name]="user?.subscription?.type"
|
||||
/>
|
||||
<div
|
||||
@ -11,14 +11,19 @@
|
||||
class="d-flex flex-column mt-5"
|
||||
>
|
||||
<ng-container
|
||||
*ngIf="hasPermissionForSubscription && hasPermissionToUpdateUserSettings"
|
||||
*ngIf="
|
||||
hasPermissionForSubscription && hasPermissionToUpdateUserSettings
|
||||
"
|
||||
>
|
||||
<button color="primary" mat-flat-button (click)="onCheckout()">
|
||||
<ng-container *ngIf="user.subscription.offer === 'default'" i18n
|
||||
>Upgrade Plan</ng-container
|
||||
>
|
||||
<ng-container
|
||||
*ngIf="user.subscription.offer === 'renewal' || user.subscription.offer === 'renewal-early-bird'"
|
||||
*ngIf="
|
||||
user.subscription.offer === 'renewal' ||
|
||||
user.subscription.offer === 'renewal-early-bird'
|
||||
"
|
||||
i18n
|
||||
>Renew Plan</ng-container
|
||||
>
|
||||
@ -27,7 +32,8 @@
|
||||
<ng-container *ngIf="coupon"
|
||||
><del class="text-muted"
|
||||
>{{ baseCurrency }} {{ price }}</del
|
||||
> {{ baseCurrency }} {{ price - coupon
|
||||
> {{ baseCurrency }} {{
|
||||
price - coupon
|
||||
}}</ng-container
|
||||
>
|
||||
<ng-container *ngIf="!coupon"
|
||||
|
@ -32,7 +32,9 @@
|
||||
name="baseCurrency"
|
||||
[disabled]="!hasPermissionToUpdateUserSettings"
|
||||
[value]="user.settings.baseCurrency"
|
||||
(selectionChange)="onChangeUserSetting('baseCurrency', $event.value)"
|
||||
(selectionChange)="
|
||||
onChangeUserSetting('baseCurrency', $event.value)
|
||||
"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let currency of currencies"
|
||||
@ -53,7 +55,9 @@
|
||||
>
|
||||
If a translation is missing, kindly support us in extending it
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/apps/client/src/locales/messages.{{language}}.xlf"
|
||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/apps/client/src/locales/messages.{{
|
||||
language
|
||||
}}.xlf"
|
||||
target="_blank"
|
||||
>here</a
|
||||
>.
|
||||
@ -65,7 +69,9 @@
|
||||
name="language"
|
||||
[disabled]="!hasPermissionToUpdateUserSettings"
|
||||
[value]="language"
|
||||
(selectionChange)="onChangeUserSetting('language', $event.value)"
|
||||
(selectionChange)="
|
||||
onChangeUserSetting('language', $event.value)
|
||||
"
|
||||
>
|
||||
<mat-option [value]="null" />
|
||||
<mat-option value="de">Deutsch</mat-option>
|
||||
@ -115,12 +121,14 @@
|
||||
name="locale"
|
||||
[disabled]="!hasPermissionToUpdateUserSettings"
|
||||
[value]="user.settings.locale"
|
||||
(selectionChange)="onChangeUserSetting('locale', $event.value)"
|
||||
(selectionChange)="
|
||||
onChangeUserSetting('locale', $event.value)
|
||||
"
|
||||
>
|
||||
<mat-option [value]="null" />
|
||||
<mat-option *ngFor="let locale of locales" [value]="locale"
|
||||
>{{ locale }}</mat-option
|
||||
>
|
||||
<mat-option *ngFor="let locale of locales" [value]="locale">{{
|
||||
locale
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@ -137,7 +145,9 @@
|
||||
[disabled]="!hasPermissionToUpdateUserSettings"
|
||||
[placeholder]="appearancePlaceholder"
|
||||
[value]="user?.settings?.colorScheme"
|
||||
(selectionChange)="onChangeUserSetting('colorScheme', $event.value)"
|
||||
(selectionChange)="
|
||||
onChangeUserSetting('colorScheme', $event.value)
|
||||
"
|
||||
>
|
||||
<mat-option i18n [value]="null">Auto</mat-option>
|
||||
<mat-option i18n value="LIGHT">Light</mat-option>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getNumberFormatGroup } from '@ghostfolio/common/helper';
|
||||
import { getLocale, getNumberFormatGroup } from '@ghostfolio/common/helper';
|
||||
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
@ -21,7 +21,7 @@ export class WorldMapChartComponent implements OnChanges, OnDestroy, OnInit {
|
||||
@Input() countries: { [code: string]: { name?: string; value: number } };
|
||||
@Input() format: string;
|
||||
@Input() isInPercent = false;
|
||||
@Input() locale: string;
|
||||
@Input() locale = getLocale();
|
||||
|
||||
public isLoading = true;
|
||||
public svgMapElement;
|
||||
|
@ -21,7 +21,7 @@
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
|
@ -11,26 +11,28 @@
|
||||
</h1>
|
||||
<div class="row">
|
||||
@for (ossFriend of ossFriends; track ossFriend) {
|
||||
<div class="col-xs-12 col-md-4 mb-3">
|
||||
<a target="_blank" [href]="ossFriend.href">
|
||||
<mat-card appearance="outlined" class="d-flex flex-column h-100">
|
||||
<mat-card-header>
|
||||
<mat-card-title class="h4">{{ ossFriend.name }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="flex-grow-1">
|
||||
<p>{{ ossFriend.description }}</p>
|
||||
</mat-card-content>
|
||||
<mat-card-actions class="justify-content-end">
|
||||
<a mat-button target="_blank" [href]="ossFriend.href">
|
||||
<span
|
||||
><ng-container i18n>Visit</ng-container> {{ ossFriend.name
|
||||
}}</span
|
||||
><ion-icon class="ml-1" name="arrow-forward-outline" />
|
||||
</a>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-4 mb-3">
|
||||
<a target="_blank" [href]="ossFriend.href">
|
||||
<mat-card appearance="outlined" class="d-flex flex-column h-100">
|
||||
<mat-card-header>
|
||||
<mat-card-title class="h4">{{
|
||||
ossFriend.name
|
||||
}}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="flex-grow-1">
|
||||
<p>{{ ossFriend.description }}</p>
|
||||
</mat-card-content>
|
||||
<mat-card-actions class="justify-content-end">
|
||||
<a mat-button target="_blank" [href]="ossFriend.href">
|
||||
<span
|
||||
><ng-container i18n>Visit</ng-container>
|
||||
{{ ossFriend.name }}</span
|
||||
><ion-icon class="ml-1" name="arrow-forward-outline" />
|
||||
</a>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -147,15 +147,15 @@
|
||||
>
|
||||
</div>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<div class="col-md-6 col-xs-12 my-2">
|
||||
<a
|
||||
class="py-4 w-100"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[routerLink]="['/blog']"
|
||||
>Blog</a
|
||||
>
|
||||
</div>
|
||||
<div class="col-md-6 col-xs-12 my-2">
|
||||
<a
|
||||
class="py-4 w-100"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[routerLink]="['/blog']"
|
||||
>Blog</a
|
||||
>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,7 +8,11 @@
|
||||
[baseCurrency]="user?.settings?.baseCurrency"
|
||||
[deviceType]="deviceType"
|
||||
[locale]="user?.settings?.locale"
|
||||
[showActions]="!hasImpersonationId && hasPermissionToUpdateAccount && !user.settings.isRestrictedView"
|
||||
[showActions]="
|
||||
!hasImpersonationId &&
|
||||
hasPermissionToUpdateAccount &&
|
||||
!user.settings.isRestrictedView
|
||||
"
|
||||
[totalBalanceInBaseCurrency]="totalBalanceInBaseCurrency"
|
||||
[totalValueInBaseCurrency]="totalValueInBaseCurrency"
|
||||
[transactionCount]="transactionCount"
|
||||
@ -21,7 +25,11 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="!hasImpersonationId && hasPermissionToCreateAccount && !user.settings.isRestrictedView"
|
||||
*ngIf="
|
||||
!hasImpersonationId &&
|
||||
hasPermissionToCreateAccount &&
|
||||
!user.settings.isRestrictedView
|
||||
"
|
||||
class="fab-container"
|
||||
>
|
||||
<a
|
||||
|
@ -5,9 +5,9 @@
|
||||
(ngSubmit)="onSubmit()"
|
||||
>
|
||||
@if (data.account.id) {
|
||||
<h1 i18n mat-dialog-title>Update account</h1>
|
||||
<h1 i18n mat-dialog-title>Update account</h1>
|
||||
} @else {
|
||||
<h1 i18n mat-dialog-title>Add account</h1>
|
||||
<h1 i18n mat-dialog-title>Add account</h1>
|
||||
}
|
||||
<div class="flex-grow-1 py-3" mat-dialog-content>
|
||||
<div>
|
||||
@ -38,9 +38,9 @@
|
||||
type="number"
|
||||
(keydown.enter)="$event.stopPropagation()"
|
||||
/>
|
||||
<span class="ml-2" matTextSuffix
|
||||
>{{ accountForm.controls['currency']?.value?.value }}</span
|
||||
>
|
||||
<span class="ml-2" matTextSuffix>{{
|
||||
accountForm.controls['currency']?.value?.value
|
||||
}}</span>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div [ngClass]="{ 'd-none': platforms?.length < 1 }">
|
||||
@ -55,18 +55,20 @@
|
||||
(keydown.enter)="$event.stopPropagation()"
|
||||
/>
|
||||
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
|
||||
@for (platformEntry of filteredPlatforms | async; track platformEntry)
|
||||
{
|
||||
<mat-option [value]="platformEntry">
|
||||
<span class="d-flex">
|
||||
<gf-symbol-icon
|
||||
class="mr-1"
|
||||
[tooltip]="platformEntry.name"
|
||||
[url]="platformEntry.url"
|
||||
/>
|
||||
<span>{{ platformEntry.name }}</span>
|
||||
</span>
|
||||
</mat-option>
|
||||
@for (
|
||||
platformEntry of filteredPlatforms | async;
|
||||
track platformEntry
|
||||
) {
|
||||
<mat-option [value]="platformEntry">
|
||||
<span class="d-flex">
|
||||
<gf-symbol-icon
|
||||
class="mr-1"
|
||||
[tooltip]="platformEntry.name"
|
||||
[url]="platformEntry.url"
|
||||
/>
|
||||
<span>{{ platformEntry.name }}</span>
|
||||
</span>
|
||||
</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
@ -89,12 +91,12 @@
|
||||
>
|
||||
</div>
|
||||
@if (data.account.id) {
|
||||
<div>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Account ID</mat-label>
|
||||
<input formControlName="accountId" matInput />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Account ID</mat-label>
|
||||
<input formControlName="accountId" matInput />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="justify-content-end" mat-dialog-actions>
|
||||
|
@ -21,7 +21,7 @@
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
|
@ -9,30 +9,30 @@
|
||||
>
|
||||
</h1>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-content>
|
||||
<div class="container p-0">
|
||||
<div class="flex-nowrap no-gutters row">
|
||||
<a
|
||||
class="d-flex overflow-hidden w-100"
|
||||
href="../en/blog/2023/11/black-week-2023"
|
||||
>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<div class="h6 m-0 text-truncate">Black Week 2023</div>
|
||||
<div class="d-flex text-muted">2023-11-19</div>
|
||||
</div>
|
||||
<div class="align-items-center d-flex">
|
||||
<ion-icon
|
||||
class="chevron text-muted"
|
||||
name="chevron-forward-outline"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-content>
|
||||
<div class="container p-0">
|
||||
<div class="flex-nowrap no-gutters row">
|
||||
<a
|
||||
class="d-flex overflow-hidden w-100"
|
||||
href="../en/blog/2023/11/black-week-2023"
|
||||
>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<div class="h6 m-0 text-truncate">Black Week 2023</div>
|
||||
<div class="d-flex text-muted">2023-11-19</div>
|
||||
</div>
|
||||
<div class="align-items-center d-flex">
|
||||
<ion-icon
|
||||
class="chevron text-muted"
|
||||
name="chevron-forward-outline"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-content>
|
||||
@ -294,30 +294,30 @@
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-content>
|
||||
<div class="container p-0">
|
||||
<div class="flex-nowrap no-gutters row">
|
||||
<a
|
||||
class="d-flex overflow-hidden w-100"
|
||||
href="../en/blog/2022/11/black-friday-2022"
|
||||
>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<div class="h6 m-0 text-truncate">Black Friday 2022</div>
|
||||
<div class="d-flex text-muted">2022-11-13</div>
|
||||
</div>
|
||||
<div class="align-items-center d-flex">
|
||||
<ion-icon
|
||||
class="chevron text-muted"
|
||||
name="chevron-forward-outline"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-content>
|
||||
<div class="container p-0">
|
||||
<div class="flex-nowrap no-gutters row">
|
||||
<a
|
||||
class="d-flex overflow-hidden w-100"
|
||||
href="../en/blog/2022/11/black-friday-2022"
|
||||
>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<div class="h6 m-0 text-truncate">Black Friday 2022</div>
|
||||
<div class="d-flex text-muted">2022-11-13</div>
|
||||
</div>
|
||||
<div class="align-items-center d-flex">
|
||||
<ion-icon
|
||||
class="chevron text-muted"
|
||||
name="chevron-forward-outline"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-content>
|
||||
|
@ -21,7 +21,7 @@
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
|
@ -140,7 +140,7 @@
|
||||
<h4 class="align-items-center d-flex">
|
||||
<span i18n>Portfolio Calculations</span>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</h4>
|
||||
<p class="m-0">
|
||||
@ -159,7 +159,7 @@
|
||||
<h4 class="align-items-center d-flex">
|
||||
<span i18n>Portfolio Allocations</span>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</h4>
|
||||
<p class="m-0">
|
||||
@ -197,24 +197,24 @@
|
||||
</mat-card>
|
||||
</div>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<div class="col-xs-12 col-md-4 mb-3">
|
||||
<mat-card appearance="outlined" class="d-flex flex-column h-100">
|
||||
<mat-card-content>
|
||||
<div class="flex-grow-1">
|
||||
<h4 class="align-items-center d-flex">
|
||||
<span i18n>Market Mood</span>
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
</h4>
|
||||
<p class="m-0">
|
||||
Check the current market mood (<a
|
||||
[routerLink]="routerLinkResources"
|
||||
>Fear & Greed Index</a
|
||||
>) within the app.
|
||||
</p>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-4 mb-3">
|
||||
<mat-card appearance="outlined" class="d-flex flex-column h-100">
|
||||
<mat-card-content>
|
||||
<div class="flex-grow-1">
|
||||
<h4 class="align-items-center d-flex">
|
||||
<span i18n>Market Mood</span>
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
</h4>
|
||||
<p class="m-0">
|
||||
Check the current market mood (<a
|
||||
[routerLink]="routerLinkResources"
|
||||
>Fear & Greed Index</a
|
||||
>) within the app.
|
||||
</p>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
}
|
||||
<div class="col-xs-12 col-md-4 mb-3">
|
||||
<mat-card appearance="outlined" class="d-flex flex-column h-100">
|
||||
@ -223,7 +223,7 @@
|
||||
<h4 class="align-items-center d-flex">
|
||||
<span i18n>Static Analysis</span>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
<gf-premium-indicator class="ml-1" />
|
||||
}
|
||||
</h4>
|
||||
<p class="m-0">
|
||||
@ -290,12 +290,16 @@
|
||||
</div>
|
||||
</div>
|
||||
@if (!user) {
|
||||
<div class="row">
|
||||
<div class="col mt-3 text-center">
|
||||
<a color="primary" i18n mat-flat-button [routerLink]="routerLinkRegister"
|
||||
>Get Started</a
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col mt-3 text-center">
|
||||
<a
|
||||
color="primary"
|
||||
i18n
|
||||
mat-flat-button
|
||||
[routerLink]="routerLinkRegister"
|
||||
>Get Started</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -21,7 +21,7 @@
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
|
@ -339,7 +339,8 @@
|
||||
[href]="testimonial.url"
|
||||
>{{ testimonial.author }}</a
|
||||
>
|
||||
<span *ngIf="!testimonial.url">{{ testimonial.author }}</span>,
|
||||
<span *ngIf="!testimonial.url">{{ testimonial.author }}</span
|
||||
>,
|
||||
{{ testimonial.country }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -11,7 +11,11 @@
|
||||
[locale]="user?.settings?.locale"
|
||||
[pageIndex]="pageIndex"
|
||||
[pageSize]="pageSize"
|
||||
[showActions]="!hasImpersonationId && hasPermissionToDeleteActivity && !user.settings.isRestrictedView"
|
||||
[showActions]="
|
||||
!hasImpersonationId &&
|
||||
hasPermissionToDeleteActivity &&
|
||||
!user.settings.isRestrictedView
|
||||
"
|
||||
[sortColumn]="sortColumn"
|
||||
[sortDirection]="sortDirection"
|
||||
[totalItems]="totalItems"
|
||||
@ -29,18 +33,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!hasImpersonationId && hasPermissionToCreateActivity &&
|
||||
!user.settings.isRestrictedView) {
|
||||
<div class="fab-container">
|
||||
<a
|
||||
class="align-items-center d-flex justify-content-center"
|
||||
color="primary"
|
||||
mat-fab
|
||||
[queryParams]="{ createDialog: true }"
|
||||
[routerLink]="[]"
|
||||
>
|
||||
<ion-icon name="add-outline" size="large" />
|
||||
</a>
|
||||
</div>
|
||||
@if (
|
||||
!hasImpersonationId &&
|
||||
hasPermissionToCreateActivity &&
|
||||
!user.settings.isRestrictedView
|
||||
) {
|
||||
<div class="fab-container">
|
||||
<a
|
||||
class="align-items-center d-flex justify-content-center"
|
||||
color="primary"
|
||||
mat-fab
|
||||
[queryParams]="{ createDialog: true }"
|
||||
[routerLink]="[]"
|
||||
>
|
||||
<ion-icon name="add-outline" size="large" />
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -11,48 +11,61 @@
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Type</mat-label>
|
||||
<mat-select formControlName="type">
|
||||
<mat-select-trigger
|
||||
>{{ typesTranslationMap[activityForm.controls['type'].value]
|
||||
}}</mat-select-trigger
|
||||
>
|
||||
<mat-select-trigger>{{
|
||||
typesTranslationMap[activityForm.controls['type'].value]
|
||||
}}</mat-select-trigger>
|
||||
<mat-option value="BUY">
|
||||
<span><b>{{ typesTranslationMap['BUY'] }}</b></span>
|
||||
<span
|
||||
><b>{{ typesTranslationMap['BUY'] }}</b></span
|
||||
>
|
||||
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
||||
>Stocks, ETFs, bonds, cryptocurrencies, commodities</small
|
||||
>
|
||||
</mat-option>
|
||||
<mat-option value="FEE">
|
||||
<span><b>{{ typesTranslationMap['FEE'] }}</b></span>
|
||||
<span
|
||||
><b>{{ typesTranslationMap['FEE'] }}</b></span
|
||||
>
|
||||
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
||||
>One-time fee, annual account fees</small
|
||||
>
|
||||
</mat-option>
|
||||
<mat-option value="DIVIDEND">
|
||||
<span><b>{{ typesTranslationMap['DIVIDEND'] }}</b></span>
|
||||
<span
|
||||
><b>{{ typesTranslationMap['DIVIDEND'] }}</b></span
|
||||
>
|
||||
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
||||
>Distribution of corporate earnings</small
|
||||
>
|
||||
</mat-option>
|
||||
<mat-option value="INTEREST">
|
||||
<span><b>{{ typesTranslationMap['INTEREST'] }}</b></span>
|
||||
<span
|
||||
><b>{{ typesTranslationMap['INTEREST'] }}</b></span
|
||||
>
|
||||
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
||||
>Revenue for lending out money</small
|
||||
>
|
||||
</mat-option>
|
||||
<mat-option value="LIABILITY">
|
||||
<span><b>{{ typesTranslationMap['LIABILITY'] }}</b></span>
|
||||
<span
|
||||
><b>{{ typesTranslationMap['LIABILITY'] }}</b></span
|
||||
>
|
||||
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
||||
>Mortgages, personal loans, credit cards</small
|
||||
>
|
||||
</mat-option>
|
||||
<mat-option value="SELL">
|
||||
<span><b>{{ typesTranslationMap['SELL'] }}</b></span>
|
||||
<span
|
||||
><b>{{ typesTranslationMap['SELL'] }}</b></span
|
||||
>
|
||||
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
||||
>Stocks, ETFs, bonds, cryptocurrencies, commodities</small
|
||||
>
|
||||
</mat-option>
|
||||
<mat-option value="ITEM">
|
||||
<span><b>{{ typesTranslationMap['ITEM'] }}</b></span>
|
||||
<span
|
||||
><b>{{ typesTranslationMap['ITEM'] }}</b></span
|
||||
>
|
||||
<small class="d-block line-height-1 text-muted text-nowrap" i18n
|
||||
>Luxury items, real estate, private companies</small
|
||||
>
|
||||
@ -60,16 +73,20 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div [ngClass]="{'mb-3': data.activity.id}">
|
||||
<div [ngClass]="{ 'mb-3': data.activity.id }">
|
||||
<mat-form-field
|
||||
appearance="outline"
|
||||
class="w-100"
|
||||
[ngClass]="{'mb-1 without-hint': !data.activity.id}"
|
||||
[ngClass]="{ 'mb-1 without-hint': !data.activity.id }"
|
||||
>
|
||||
<mat-label i18n>Account</mat-label>
|
||||
<mat-select formControlName="accountId">
|
||||
<mat-option
|
||||
*ngIf="!activityForm.controls['accountId'].hasValidator(Validators.required)"
|
||||
*ngIf="
|
||||
!activityForm.controls['accountId'].hasValidator(
|
||||
Validators.required
|
||||
)
|
||||
"
|
||||
[value]="null"
|
||||
/>
|
||||
<mat-option
|
||||
@ -88,14 +105,18 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="mb-3" [ngClass]="{'d-none': data.activity.id}">
|
||||
<div class="mb-3" [ngClass]="{ 'd-none': data.activity.id }">
|
||||
<mat-checkbox color="primary" formControlName="updateAccountBalance" i18n
|
||||
>Update Cash Balance</mat-checkbox
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="mb-3"
|
||||
[ngClass]="{ 'd-none': !activityForm.controls['searchSymbol'].hasValidator(Validators.required) }"
|
||||
[ngClass]="{
|
||||
'd-none': !activityForm.controls['searchSymbol'].hasValidator(
|
||||
Validators.required
|
||||
)
|
||||
}"
|
||||
>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Name, symbol or ISIN</mat-label>
|
||||
@ -107,7 +128,11 @@
|
||||
</div>
|
||||
<div
|
||||
class="mb-3"
|
||||
[ngClass]="{ 'd-none': !activityForm.controls['name'].hasValidator(Validators.required) }"
|
||||
[ngClass]="{
|
||||
'd-none': !activityForm.controls['name'].hasValidator(
|
||||
Validators.required
|
||||
)
|
||||
}"
|
||||
>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Name</mat-label>
|
||||
@ -118,9 +143,9 @@
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Currency</mat-label>
|
||||
<mat-select formControlName="currency">
|
||||
<mat-option *ngFor="let currency of currencies" [value]="currency"
|
||||
>{{ currency }}</mat-option
|
||||
>
|
||||
<mat-option *ngFor="let currency of currencies" [value]="currency">{{
|
||||
currency
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@ -146,7 +171,13 @@
|
||||
</div>
|
||||
<div
|
||||
class="mb-3"
|
||||
[ngClass]="{ 'd-none': activityForm.controls['type']?.value === 'FEE' || activityForm.controls['type']?.value === 'INTEREST' || activityForm.controls['type']?.value === 'ITEM' || activityForm.controls['type']?.value === 'LIABILITY' }"
|
||||
[ngClass]="{
|
||||
'd-none':
|
||||
activityForm.controls['type']?.value === 'FEE' ||
|
||||
activityForm.controls['type']?.value === 'INTEREST' ||
|
||||
activityForm.controls['type']?.value === 'ITEM' ||
|
||||
activityForm.controls['type']?.value === 'LIABILITY'
|
||||
}"
|
||||
>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Quantity</mat-label>
|
||||
@ -155,7 +186,7 @@
|
||||
</div>
|
||||
<div
|
||||
class="mb-3"
|
||||
[ngClass]="{ 'd-none': activityForm.controls['type']?.value === 'FEE' }"
|
||||
[ngClass]="{ 'd-none': activityForm.controls['type']?.value === 'FEE' }"
|
||||
>
|
||||
<div class="align-items-start d-flex">
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
@ -192,17 +223,25 @@
|
||||
</mat-select>
|
||||
</div>
|
||||
<mat-error
|
||||
*ngIf="activityForm.controls['unitPriceInCustomCurrency'].hasError('invalid')"
|
||||
*ngIf="
|
||||
activityForm.controls['unitPriceInCustomCurrency'].hasError(
|
||||
'invalid'
|
||||
)
|
||||
"
|
||||
><ng-container i18n
|
||||
>Oops! Could not get the historical exchange rate
|
||||
from</ng-container
|
||||
>
|
||||
{{ activityForm.controls['date']?.value | date: defaultDateFormat
|
||||
{{
|
||||
activityForm.controls['date']?.value | date: defaultDateFormat
|
||||
}}</mat-error
|
||||
>
|
||||
</mat-form-field>
|
||||
<button
|
||||
*ngIf="currentMarketPrice && (data.activity.type === 'BUY' || data.activity.type === 'SELL')"
|
||||
*ngIf="
|
||||
currentMarketPrice &&
|
||||
(data.activity.type === 'BUY' || data.activity.type === 'SELL')
|
||||
"
|
||||
class="ml-2 mt-1 no-min-width"
|
||||
mat-button
|
||||
title="Apply current market price"
|
||||
@ -228,14 +267,19 @@
|
||||
</ng-container>
|
||||
</mat-label>
|
||||
<input formControlName="unitPrice" matInput type="number" />
|
||||
<span class="ml-2" matTextSuffix
|
||||
>{{ activityForm.controls['currency'].value }}</span
|
||||
>
|
||||
<span class="ml-2" matTextSuffix>{{
|
||||
activityForm.controls['currency'].value
|
||||
}}</span>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div
|
||||
class="mb-3"
|
||||
[ngClass]="{ 'd-none': activityForm.controls['type']?.value === 'INTEREST' || activityForm.controls['type']?.value === 'ITEM' || activityForm.controls['type']?.value === 'LIABILITY' }"
|
||||
[ngClass]="{
|
||||
'd-none':
|
||||
activityForm.controls['type']?.value === 'INTEREST' ||
|
||||
activityForm.controls['type']?.value === 'ITEM' ||
|
||||
activityForm.controls['type']?.value === 'LIABILITY'
|
||||
}"
|
||||
>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Fee</mat-label>
|
||||
@ -252,11 +296,14 @@
|
||||
</mat-select>
|
||||
</div>
|
||||
<mat-error
|
||||
*ngIf="activityForm.controls['feeInCustomCurrency'].hasError('invalid')"
|
||||
*ngIf="
|
||||
activityForm.controls['feeInCustomCurrency'].hasError('invalid')
|
||||
"
|
||||
><ng-container i18n
|
||||
>Oops! Could not get the historical exchange rate from</ng-container
|
||||
>
|
||||
{{ activityForm.controls['date']?.value | date: defaultDateFormat
|
||||
{{
|
||||
activityForm.controls['date']?.value | date: defaultDateFormat
|
||||
}}</mat-error
|
||||
>
|
||||
</mat-form-field>
|
||||
@ -265,9 +312,9 @@
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Fee</mat-label>
|
||||
<input formControlName="fee" matInput type="number" />
|
||||
<span class="ml-2" matTextSuffix
|
||||
>{{ activityForm.controls['currency'].value }}</span
|
||||
>
|
||||
<span class="ml-2" matTextSuffix>{{
|
||||
activityForm.controls['currency'].value
|
||||
}}</span>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
@ -340,7 +387,7 @@
|
||||
(optionSelected)="onAddTag($event)"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let tag of filteredTagsObservable | async"
|
||||
*ngFor="let tag of filteredTagsObservable | async"
|
||||
[value]="tag.id"
|
||||
>
|
||||
{{ tag.name }}
|
||||
@ -354,7 +401,10 @@
|
||||
class="flex-grow-1"
|
||||
[isCurrency]="true"
|
||||
[locale]="data.user?.settings?.locale"
|
||||
[unit]="activityForm.controls['currency']?.value ?? data.user?.settings?.baseCurrency"
|
||||
[unit]="
|
||||
activityForm.controls['currency']?.value ??
|
||||
data.user?.settings?.baseCurrency
|
||||
"
|
||||
[value]="total"
|
||||
/>
|
||||
<div>
|
||||
|
@ -25,81 +25,86 @@
|
||||
</ng-template>
|
||||
<div class="pt-3">
|
||||
@if (mode === 'DIVIDEND') {
|
||||
<form
|
||||
[formGroup]="uniqueAssetForm"
|
||||
(ngSubmit)="onLoadDividends(stepper)"
|
||||
>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Holding</mat-label>
|
||||
<mat-select formControlName="uniqueAsset">
|
||||
<mat-select-trigger
|
||||
>{{ uniqueAssetForm.controls['uniqueAsset']?.value?.name
|
||||
}}</mat-select-trigger
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let holding of holdings"
|
||||
class="line-height-1"
|
||||
[value]="{ dataSource: holding.dataSource, name: holding.name, symbol: holding.symbol }"
|
||||
>
|
||||
<span><b>{{ holding.name }}</b></span>
|
||||
<br />
|
||||
<small class="text-muted"
|
||||
>{{ holding.symbol | gfSymbol }} · {{ holding.currency
|
||||
}}</small
|
||||
<form
|
||||
[formGroup]="uniqueAssetForm"
|
||||
(ngSubmit)="onLoadDividends(stepper)"
|
||||
>
|
||||
<mat-form-field appearance="outline" class="w-100">
|
||||
<mat-label i18n>Holding</mat-label>
|
||||
<mat-select formControlName="uniqueAsset">
|
||||
<mat-select-trigger>{{
|
||||
uniqueAssetForm.controls['uniqueAsset']?.value?.name
|
||||
}}</mat-select-trigger>
|
||||
<mat-option
|
||||
*ngFor="let holding of holdings"
|
||||
class="line-height-1"
|
||||
[value]="{
|
||||
dataSource: holding.dataSource,
|
||||
name: holding.name,
|
||||
symbol: holding.symbol
|
||||
}"
|
||||
>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-spinner
|
||||
*ngIf="isLoading"
|
||||
class="position-absolute"
|
||||
[diameter]="20"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<span
|
||||
><b>{{ holding.name }}</b></span
|
||||
>
|
||||
<br />
|
||||
<small class="text-muted"
|
||||
>{{ holding.symbol | gfSymbol }} ·
|
||||
{{ holding.currency }}</small
|
||||
>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
<mat-spinner
|
||||
*ngIf="isLoading"
|
||||
class="position-absolute"
|
||||
[diameter]="20"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<div class="d-flex flex-column justify-content-center">
|
||||
<button
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
type="submit"
|
||||
[disabled]="!uniqueAssetForm.valid"
|
||||
>
|
||||
<span i18n>Load Dividends</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
} @else {
|
||||
<div class="d-flex flex-column justify-content-center">
|
||||
<button
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
type="submit"
|
||||
[disabled]="!uniqueAssetForm.valid"
|
||||
class="drop-area p-4 text-center text-muted"
|
||||
gfFileDrop
|
||||
(click)="onSelectFile(stepper)"
|
||||
(filesDropped)="onFilesDropped({ stepper, files: $event })"
|
||||
>
|
||||
<span i18n>Load Dividends</span>
|
||||
<div
|
||||
class="align-items-center d-flex flex-column justify-content-center"
|
||||
>
|
||||
<ion-icon class="cloud-icon" name="cloud-upload-outline" />
|
||||
<span i18n>Choose or drop a file here</span>
|
||||
</div>
|
||||
</button>
|
||||
<p class="mb-0 mt-3 text-center">
|
||||
<small>
|
||||
<span class="mr-1" i18n
|
||||
>The following file formats are supported:</span
|
||||
>
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok.csv"
|
||||
target="_blank"
|
||||
>CSV</a
|
||||
>
|
||||
<span class="mx-1" i18n>or</span>
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok.json"
|
||||
target="_blank"
|
||||
>JSON</a
|
||||
>
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
} @else {
|
||||
<div class="d-flex flex-column justify-content-center">
|
||||
<button
|
||||
class="drop-area p-4 text-center text-muted"
|
||||
gfFileDrop
|
||||
(click)="onSelectFile(stepper)"
|
||||
(filesDropped)="onFilesDropped({stepper, files: $event})"
|
||||
>
|
||||
<div
|
||||
class="align-items-center d-flex flex-column justify-content-center"
|
||||
>
|
||||
<ion-icon class="cloud-icon" name="cloud-upload-outline" />
|
||||
<span i18n>Choose or drop a file here</span>
|
||||
</div>
|
||||
</button>
|
||||
<p class="mb-0 mt-3 text-center">
|
||||
<small>
|
||||
<span class="mr-1" i18n
|
||||
>The following file formats are supported:</span
|
||||
>
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok.csv"
|
||||
target="_blank"
|
||||
>CSV</a
|
||||
>
|
||||
<span class="mx-1" i18n>or</span>
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok.json"
|
||||
target="_blank"
|
||||
>JSON</a
|
||||
>
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</mat-step>
|
||||
@ -114,76 +119,76 @@
|
||||
>
|
||||
</ng-template>
|
||||
<div class="pt-3">
|
||||
@if(errorMessages?.length === 0) {
|
||||
<gf-activities-table
|
||||
*ngIf="importStep === 1"
|
||||
[baseCurrency]="data?.user?.settings?.baseCurrency"
|
||||
[dataSource]="dataSource"
|
||||
[deviceType]="data?.deviceType"
|
||||
[hasPermissionToCreateActivity]="false"
|
||||
[hasPermissionToExportActivities]="false"
|
||||
[hasPermissionToFilter]="false"
|
||||
[hasPermissionToOpenDetails]="false"
|
||||
[locale]="data?.user?.settings?.locale"
|
||||
[pageSize]="maxSafeInteger"
|
||||
[showActions]="false"
|
||||
[showCheckbox]="true"
|
||||
[showSymbolColumn]="false"
|
||||
[sortColumn]="sortColumn"
|
||||
[sortDirection]="sortDirection"
|
||||
[sortDisabled]="true"
|
||||
[totalItems]="totalItems"
|
||||
(selectedActivities)="updateSelection($event)"
|
||||
/>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button mat-button (click)="onReset(stepper)">
|
||||
<ng-container i18n>Back</ng-container>
|
||||
</button>
|
||||
<button
|
||||
class="ml-1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[disabled]="!selectedActivities?.length"
|
||||
(click)="onImportActivities()"
|
||||
>
|
||||
<ng-container i18n>Import</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
@if (errorMessages?.length === 0) {
|
||||
<gf-activities-table
|
||||
*ngIf="importStep === 1"
|
||||
[baseCurrency]="data?.user?.settings?.baseCurrency"
|
||||
[dataSource]="dataSource"
|
||||
[deviceType]="data?.deviceType"
|
||||
[hasPermissionToCreateActivity]="false"
|
||||
[hasPermissionToExportActivities]="false"
|
||||
[hasPermissionToFilter]="false"
|
||||
[hasPermissionToOpenDetails]="false"
|
||||
[locale]="data?.user?.settings?.locale"
|
||||
[pageSize]="maxSafeInteger"
|
||||
[showActions]="false"
|
||||
[showCheckbox]="true"
|
||||
[showSymbolColumn]="false"
|
||||
[sortColumn]="sortColumn"
|
||||
[sortDirection]="sortDirection"
|
||||
[sortDisabled]="true"
|
||||
[totalItems]="totalItems"
|
||||
(selectedActivities)="updateSelection($event)"
|
||||
/>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button mat-button (click)="onReset(stepper)">
|
||||
<ng-container i18n>Back</ng-container>
|
||||
</button>
|
||||
<button
|
||||
class="ml-1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[disabled]="!selectedActivities?.length"
|
||||
(click)="onImportActivities()"
|
||||
>
|
||||
<ng-container i18n>Import</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<mat-accordion displayMode="flat">
|
||||
<mat-expansion-panel
|
||||
*ngFor="let message of errorMessages; let i = index"
|
||||
[disabled]="!details[i]"
|
||||
>
|
||||
<mat-expansion-panel-header class="pl-1">
|
||||
<mat-panel-title>
|
||||
<div class="d-flex">
|
||||
<div class="align-items-center d-flex mr-2">
|
||||
<ion-icon name="warning-outline" />
|
||||
<mat-accordion displayMode="flat">
|
||||
<mat-expansion-panel
|
||||
*ngFor="let message of errorMessages; let i = index"
|
||||
[disabled]="!details[i]"
|
||||
>
|
||||
<mat-expansion-panel-header class="pl-1">
|
||||
<mat-panel-title>
|
||||
<div class="d-flex">
|
||||
<div class="align-items-center d-flex mr-2">
|
||||
<ion-icon name="warning-outline" />
|
||||
</div>
|
||||
<div>{{ message }}</div>
|
||||
</div>
|
||||
<div>{{ message }}</div>
|
||||
</div>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<pre
|
||||
*ngIf="details[i]"
|
||||
class="m-0"
|
||||
><code>{{ details[i] | json }}</code></pre>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button mat-button (click)="onReset(stepper)">
|
||||
<ng-container i18n>Back</ng-container>
|
||||
</button>
|
||||
<button
|
||||
class="ml-1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[disabled]="true"
|
||||
>
|
||||
<ng-container i18n>Import</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<pre
|
||||
*ngIf="details[i]"
|
||||
class="m-0"
|
||||
><code>{{ details[i] | json }}</code></pre>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button mat-button (click)="onReset(stepper)">
|
||||
<ng-container i18n>Back</ng-container>
|
||||
</button>
|
||||
<button
|
||||
class="ml-1"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
[disabled]="true"
|
||||
>
|
||||
<ng-container i18n>Import</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</mat-step>
|
||||
|
@ -15,13 +15,20 @@
|
||||
class="justify-content-end l-2"
|
||||
size="medium"
|
||||
[isPercent]="true"
|
||||
[value]="isLoading ? undefined : portfolioDetails?.filteredValueInPercentage"
|
||||
[value]="
|
||||
isLoading
|
||||
? undefined
|
||||
: portfolioDetails?.filteredValueInPercentage
|
||||
"
|
||||
/>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<mat-progress-bar
|
||||
mode="determinate"
|
||||
[title]="(portfolioDetails?.filteredValueInPercentage * 100).toFixed(2) + '%'"
|
||||
[title]="
|
||||
(portfolioDetails?.filteredValueInPercentage * 100).toFixed(2) +
|
||||
'%'
|
||||
"
|
||||
[value]="portfolioDetails?.filteredValueInPercentage * 100"
|
||||
/>
|
||||
</mat-card-content>
|
||||
@ -204,7 +211,9 @@
|
||||
<gf-world-map-chart
|
||||
[countries]="countries"
|
||||
[format]="worldMapChartFormat"
|
||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||
[isInPercent]="
|
||||
hasImpersonationId || user.settings.isRestrictedView
|
||||
"
|
||||
[locale]="user?.settings?.locale"
|
||||
/>
|
||||
</div>
|
||||
|
@ -19,105 +19,134 @@
|
||||
</div>
|
||||
|
||||
@if (user?.settings?.isExperimentalFeatures) {
|
||||
<div class="mb-5 row">
|
||||
<div class="col">
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-content>
|
||||
<div class="d-flex py-1">
|
||||
<div class="flex-grow-1 mr-2 text-truncate" i18n>
|
||||
Absolute Asset Performance
|
||||
<div class="mb-5 row">
|
||||
<div class="col">
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-content>
|
||||
<div class="d-flex py-1">
|
||||
<div class="flex-grow-1 mr-2 text-truncate" i18n>
|
||||
Absolute Asset Performance
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[isCurrency]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[unit]="user?.settings?.baseCurrency"
|
||||
[value]="
|
||||
isLoadingInvestmentChart
|
||||
? undefined
|
||||
: performance?.currentNetPerformance
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[isCurrency]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[unit]="user?.settings?.baseCurrency"
|
||||
[value]="isLoadingInvestmentChart ? undefined : performance?.currentNetPerformance"
|
||||
/>
|
||||
<div class="d-flex mb-3 ml-3 py-1">
|
||||
<div class="flex-grow-1 mr-2 text-truncate" i18n>
|
||||
Asset Performance
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[colorizeSign]="true"
|
||||
[isPercent]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="
|
||||
isLoadingInvestmentChart
|
||||
? undefined
|
||||
: performance?.currentNetPerformancePercent
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex mb-3 ml-3 py-1">
|
||||
<div class="flex-grow-1 mr-2 text-truncate" i18n>
|
||||
Asset Performance
|
||||
<div class="d-flex py-1">
|
||||
<div class="flex-grow-1 mr-2 text-truncate" i18n>
|
||||
Absolute Currency Performance
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[isCurrency]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[unit]="user?.settings?.baseCurrency"
|
||||
[value]="
|
||||
isLoadingInvestmentChart
|
||||
? undefined
|
||||
: performance?.currentNetPerformanceWithCurrencyEffect ===
|
||||
null
|
||||
? null
|
||||
: performance?.currentNetPerformanceWithCurrencyEffect -
|
||||
performance?.currentNetPerformance
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[colorizeSign]="true"
|
||||
[isPercent]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="isLoadingInvestmentChart ? undefined : performance?.currentNetPerformancePercent"
|
||||
/>
|
||||
<div class="d-flex ml-3 py-1">
|
||||
<div class="flex-grow-1 mr-2 text-truncate" i18n>
|
||||
Currency Performance
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[colorizeSign]="true"
|
||||
[isPercent]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="
|
||||
isLoadingInvestmentChart
|
||||
? undefined
|
||||
: performance?.currentNetPerformancePercentWithCurrencyEffect -
|
||||
performance?.currentNetPerformancePercent
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex py-1">
|
||||
<div class="flex-grow-1 mr-2 text-truncate" i18n>
|
||||
Absolute Currency Performance
|
||||
<div><hr /></div>
|
||||
<div class="d-flex py-1">
|
||||
<div class="flex-grow-1 mr-2 text-truncate" i18n>
|
||||
Absolute Net Performance
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[isCurrency]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[unit]="user?.settings?.baseCurrency"
|
||||
[value]="
|
||||
isLoadingInvestmentChart
|
||||
? undefined
|
||||
: performance?.currentNetPerformanceWithCurrencyEffect
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[isCurrency]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[unit]="user?.settings?.baseCurrency"
|
||||
[value]="isLoadingInvestmentChart ? undefined : (performance?.currentNetPerformanceWithCurrencyEffect === null ? null : performance?.currentNetPerformanceWithCurrencyEffect - performance?.currentNetPerformance)"
|
||||
/>
|
||||
<div class="d-flex ml-3 py-1">
|
||||
<div class="flex-grow-1 mr-2 text-truncate" i18n>
|
||||
Net Performance
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[colorizeSign]="true"
|
||||
[isPercent]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="
|
||||
isLoadingInvestmentChart
|
||||
? undefined
|
||||
: performance?.currentNetPerformancePercentWithCurrencyEffect
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex ml-3 py-1">
|
||||
<div class="flex-grow-1 mr-2 text-truncate" i18n>
|
||||
Currency Performance
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[colorizeSign]="true"
|
||||
[isPercent]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="isLoadingInvestmentChart ? undefined : performance?.currentNetPerformancePercentWithCurrencyEffect - performance?.currentNetPerformancePercent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div><hr /></div>
|
||||
<div class="d-flex py-1">
|
||||
<div class="flex-grow-1 mr-2 text-truncate" i18n>
|
||||
Absolute Net Performance
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[isCurrency]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[unit]="user?.settings?.baseCurrency"
|
||||
[value]="isLoadingInvestmentChart ? undefined : performance?.currentNetPerformanceWithCurrencyEffect"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex ml-3 py-1">
|
||||
<div class="flex-grow-1 mr-2 text-truncate" i18n>
|
||||
Net Performance
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
position="end"
|
||||
[colorizeSign]="true"
|
||||
[isPercent]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="isLoadingInvestmentChart ? undefined : performance?.currentNetPerformancePercentWithCurrencyEffect"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="mb-5 row">
|
||||
|
@ -16,11 +16,14 @@
|
||||
[currency]="user?.settings?.baseCurrency"
|
||||
[deviceType]="deviceType"
|
||||
[fireWealth]="fireWealth?.toNumber()"
|
||||
[hasPermissionToUpdateUserSettings]="!hasImpersonationId && hasPermissionToUpdateUserSettings"
|
||||
[hasPermissionToUpdateUserSettings]="
|
||||
!hasImpersonationId && hasPermissionToUpdateUserSettings
|
||||
"
|
||||
[locale]="user?.settings?.locale"
|
||||
[ngStyle]="{
|
||||
opacity: user?.subscription?.type === 'Basic' ? '0.67' : 'initial',
|
||||
'pointer-events': user?.subscription?.type === 'Basic' ? 'none' : 'initial'
|
||||
'pointer-events':
|
||||
user?.subscription?.type === 'Basic' ? 'none' : 'initial'
|
||||
}"
|
||||
[projectedTotalAmount]="user?.settings?.projectedTotalAmount"
|
||||
[retirementDate]="user?.settings?.retirementDate"
|
||||
|
@ -14,15 +14,15 @@
|
||||
[locale]="user?.settings?.locale"
|
||||
/>
|
||||
@if (hasPermissionToCreateOrder && holdings?.length > 0) {
|
||||
<div class="text-center">
|
||||
<a
|
||||
class="mt-3"
|
||||
i18n
|
||||
mat-stroked-button
|
||||
[routerLink]="['/portfolio', 'activities']"
|
||||
>Manage Activities</a
|
||||
>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<a
|
||||
class="mt-3"
|
||||
i18n
|
||||
mat-stroked-button
|
||||
[routerLink]="['/portfolio', 'activities']"
|
||||
>Manage Activities</a
|
||||
>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -21,7 +21,7 @@
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
|
@ -107,7 +107,7 @@
|
||||
<mat-card
|
||||
appearance="outlined"
|
||||
class="h-100"
|
||||
[ngClass]="{ 'active': user?.subscription?.type === 'Basic' }"
|
||||
[ngClass]="{ active: user?.subscription?.type === 'Basic' }"
|
||||
>
|
||||
<mat-card-content class="d-flex flex-column h-100">
|
||||
<div class="flex-grow-1">
|
||||
@ -164,7 +164,7 @@
|
||||
<mat-card
|
||||
appearance="outlined"
|
||||
class="h-100"
|
||||
[ngClass]="{ 'active': user?.subscription?.type === 'Premium' }"
|
||||
[ngClass]="{ active: user?.subscription?.type === 'Premium' }"
|
||||
>
|
||||
<mat-card-content class="d-flex flex-column h-100">
|
||||
<div class="flex-grow-1">
|
||||
@ -243,19 +243,22 @@
|
||||
<ng-container *ngIf="coupon"
|
||||
><del class="text-muted"
|
||||
>{{ baseCurrency }} {{ price }}</del
|
||||
> {{ baseCurrency }} <strong
|
||||
>{{ price - coupon }}</strong
|
||||
>
|
||||
> {{ baseCurrency }} <strong>{{
|
||||
price - coupon
|
||||
}}</strong>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!coupon"
|
||||
>{{ baseCurrency }} <strong
|
||||
>{{ price }}</strong
|
||||
></ng-container
|
||||
>{{ baseCurrency }} <strong>{{
|
||||
price
|
||||
}}</strong></ng-container
|
||||
> <span i18n>per year</span></span
|
||||
>
|
||||
</p>
|
||||
<div
|
||||
*ngIf="hasPermissionToUpdateUserSettings && user?.subscription?.type === 'Basic'"
|
||||
*ngIf="
|
||||
hasPermissionToUpdateUserSettings &&
|
||||
user?.subscription?.type === 'Basic'
|
||||
"
|
||||
class="mt-3 text-center"
|
||||
>
|
||||
<button color="primary" mat-flat-button (click)="onCheckout()">
|
||||
@ -265,7 +268,10 @@
|
||||
>Upgrade Plan</ng-container
|
||||
>
|
||||
<ng-container
|
||||
*ngIf="user.subscription.offer === 'renewal' || user.subscription.offer === 'renewal-early-bird'"
|
||||
*ngIf="
|
||||
user.subscription.offer === 'renewal' ||
|
||||
user.subscription.offer === 'renewal-early-bird'
|
||||
"
|
||||
i18n
|
||||
>Renew Plan</ng-container
|
||||
>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<h1 mat-dialog-title>
|
||||
<span i18n>Create Account</span
|
||||
><span *ngIf="data.role === 'ADMIN'" class="badge badge-light ml-2"
|
||||
>{{ data.role }}</span
|
||||
>
|
||||
><span *ngIf="data.role === 'ADMIN'" class="badge badge-light ml-2">{{
|
||||
data.role
|
||||
}}</span>
|
||||
</h1>
|
||||
<div class="py-3" mat-dialog-content>
|
||||
<div>
|
||||
|
@ -19,32 +19,38 @@
|
||||
</p>
|
||||
</div>
|
||||
@for (product of products; track product) {
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-content>
|
||||
<div class="container p-0">
|
||||
<div class="flex-nowrap no-gutters row">
|
||||
<a
|
||||
class="d-flex overflow-hidden w-100"
|
||||
title="Compare Ghostfolio to {{ product.name }} - {{ product.slogan }}"
|
||||
[routerLink]="[pathResources, 'personal-finance-tools', pathAlternativeTo + (product.alias ?? product.key)]"
|
||||
>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<div class="h6 m-0 text-truncate" i18n>
|
||||
Open Source Alternative to {{ product.name }}
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-content>
|
||||
<div class="container p-0">
|
||||
<div class="flex-nowrap no-gutters row">
|
||||
<a
|
||||
class="d-flex overflow-hidden w-100"
|
||||
title="Compare Ghostfolio to {{ product.name }} - {{
|
||||
product.slogan
|
||||
}}"
|
||||
[routerLink]="[
|
||||
pathResources,
|
||||
'personal-finance-tools',
|
||||
pathAlternativeTo + (product.alias ?? product.key)
|
||||
]"
|
||||
>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<div class="h6 m-0 text-truncate" i18n>
|
||||
Open Source Alternative to {{ product.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="align-items-center d-flex">
|
||||
<ion-icon
|
||||
class="chevron text-muted"
|
||||
name="chevron-forward-outline"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
<div class="align-items-center d-flex">
|
||||
<ion-icon
|
||||
class="chevron text-muted"
|
||||
name="chevron-forward-outline"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -11,8 +11,9 @@
|
||||
</div>
|
||||
<section class="mb-4">
|
||||
<p i18n>
|
||||
Are you looking for an open source alternative to {{ product2.name
|
||||
}}? <a [routerLink]="routerLinkAbout">Ghostfolio</a> is a powerful
|
||||
Are you looking for an open source alternative to
|
||||
{{ product2.name }}?
|
||||
<a [routerLink]="routerLinkAbout">Ghostfolio</a> is a powerful
|
||||
portfolio management tool that provides individuals with a
|
||||
comprehensive platform to track, analyze, and optimize their
|
||||
investments. Whether you are an experienced investor or just
|
||||
@ -35,18 +36,22 @@
|
||||
its capabilities, security, and user experience.
|
||||
</p>
|
||||
<p i18n>
|
||||
Let’s dive deeper into the detailed Ghostfolio vs {{ product2.name
|
||||
}} comparison table below to gain a thorough understanding of how
|
||||
Ghostfolio positions itself relative to {{ product2.name }}. We will
|
||||
explore various aspects such as features, data privacy, pricing, and
|
||||
more, allowing you to make a well-informed choice for your personal
|
||||
requirements.
|
||||
Let’s dive deeper into the detailed Ghostfolio vs
|
||||
{{ product2.name }} comparison table below to gain a thorough
|
||||
understanding of how Ghostfolio positions itself relative to
|
||||
{{ product2.name }}. We will explore various aspects such as
|
||||
features, data privacy, pricing, and more, allowing you to make a
|
||||
well-informed choice for your personal requirements.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<table class="gf-table w-100">
|
||||
<caption class="text-center" i18n>
|
||||
Ghostfolio vs {{ product2.name }} comparison table
|
||||
Ghostfolio vs
|
||||
{{
|
||||
product2.name
|
||||
}}
|
||||
comparison table
|
||||
</caption>
|
||||
<thead>
|
||||
<tr class="mat-mdc-header-row">
|
||||
@ -187,8 +192,9 @@
|
||||
</td>
|
||||
<td class="mat-mdc-cell px-1 py-2">
|
||||
<ng-container *ngIf="product2.pricingPerYear"
|
||||
><span i18n>Starting from</span> {{ product2.pricingPerYear
|
||||
}} / <span i18n>year</span></ng-container
|
||||
><span i18n>Starting from</span>
|
||||
{{ product2.pricingPerYear }} /
|
||||
<span i18n>year</span></ng-container
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
@ -202,13 +208,13 @@
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<p i18n>
|
||||
Please note that the information provided in the Ghostfolio vs {{
|
||||
product2.name }} comparison table is based on our independent
|
||||
research and analysis. This website is not affiliated with {{
|
||||
product2.name }} or any other product mentioned in the comparison.
|
||||
As the landscape of personal finance tools evolves, it is essential
|
||||
to verify any specific details or changes directly from the
|
||||
respective product page. Data needs a refresh? Help us maintain
|
||||
Please note that the information provided in the Ghostfolio vs
|
||||
{{ product2.name }} comparison table is based on our independent
|
||||
research and analysis. This website is not affiliated with
|
||||
{{ product2.name }} or any other product mentioned in the
|
||||
comparison. As the landscape of personal finance tools evolves, it
|
||||
is essential to verify any specific details or changes directly from
|
||||
the respective product page. Data needs a refresh? Help us maintain
|
||||
accurate data on
|
||||
<a href="https://github.com/ghostfolio/ghostfolio">GitHub</a>.
|
||||
</p>
|
||||
|
@ -171,21 +171,21 @@
|
||||
</div>
|
||||
</div>
|
||||
@if (hasPermissionForSubscription) {
|
||||
<div class="mb-4 media">
|
||||
<div class="media-body">
|
||||
<h3 class="h5 mt-0">Personal Finance Tools</h3>
|
||||
<div class="mb-1">
|
||||
Personal finance tools are software applications that help
|
||||
individuals manage their money, track expenses, set budgets,
|
||||
monitor investments, and make informed financial decisions.
|
||||
</div>
|
||||
<div>
|
||||
<a [routerLink]="routerLinkResourcesPersonalFinanceTools"
|
||||
>Personal Finance Tools →</a
|
||||
>
|
||||
<div class="mb-4 media">
|
||||
<div class="media-body">
|
||||
<h3 class="h5 mt-0">Personal Finance Tools</h3>
|
||||
<div class="mb-1">
|
||||
Personal finance tools are software applications that help
|
||||
individuals manage their money, track expenses, set budgets,
|
||||
monitor investments, and make informed financial decisions.
|
||||
</div>
|
||||
<div>
|
||||
<a [routerLink]="routerLinkResourcesPersonalFinanceTools"
|
||||
>Personal Finance Tools →</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="mb-4 media">
|
||||
<div class="media-body">
|
||||
|
@ -21,7 +21,7 @@
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
|
@ -8,29 +8,29 @@
|
||||
</div>
|
||||
|
||||
@if (!hasError) {
|
||||
<div class="col d-flex justify-content-center">
|
||||
<mat-spinner [diameter]="20" />
|
||||
</div>
|
||||
<div class="col d-flex justify-content-center">
|
||||
<mat-spinner [diameter]="20" />
|
||||
</div>
|
||||
} @else {
|
||||
<div
|
||||
class="align-items-center col d-flex flex-column justify-content-center"
|
||||
>
|
||||
<h1 class="d-flex h5 justify-content-center mb-0 text-center">
|
||||
<ng-container i18n>Oops, authentication has failed.</ng-container>
|
||||
</h1>
|
||||
<button
|
||||
class="mb-3 mt-4"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
(click)="signIn()"
|
||||
<div
|
||||
class="align-items-center col d-flex flex-column justify-content-center"
|
||||
>
|
||||
<ng-container i18n>Try again</ng-container>
|
||||
</button>
|
||||
<div class="text-muted"><ng-container i18n>or</ng-container></div>
|
||||
<button class="mt-1" mat-flat-button (click)="deregisterDevice()">
|
||||
<ng-container i18n>Go back to Home Page</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="d-flex h5 justify-content-center mb-0 text-center">
|
||||
<ng-container i18n>Oops, authentication has failed.</ng-container>
|
||||
</h1>
|
||||
<button
|
||||
class="mb-3 mt-4"
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
(click)="signIn()"
|
||||
>
|
||||
<ng-container i18n>Try again</ng-container>
|
||||
</button>
|
||||
<div class="text-muted"><ng-container i18n>or</ng-container></div>
|
||||
<button class="mt-1" mat-flat-button (click)="deregisterDevice()">
|
||||
<ng-container i18n>Go back to Home Page</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -21,7 +21,7 @@
|
||||
>
|
||||
<ion-icon
|
||||
[name]="tab.iconName"
|
||||
[size]="deviceType === 'mobile' ? 'large': 'small'"
|
||||
[size]="deviceType === 'mobile' ? 'large' : 'small'"
|
||||
/>
|
||||
<div class="d-none d-sm-block ml-2">{{ tab.label }}</div>
|
||||
</a>
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
DATE_FORMAT_MONTHLY,
|
||||
DATE_FORMAT_YEARLY,
|
||||
getBackgroundColor,
|
||||
getLocale,
|
||||
getTextColor
|
||||
} from './helper';
|
||||
import { ColorScheme, GroupBy } from './types';
|
||||
@ -30,7 +31,7 @@ export function getTooltipOptions({
|
||||
colorScheme,
|
||||
currency = '',
|
||||
groupBy,
|
||||
locale = '',
|
||||
locale = getLocale(),
|
||||
unit = ''
|
||||
}: {
|
||||
colorScheme?: ColorScheme;
|
||||
|
@ -217,9 +217,7 @@ export function getEmojiFlag(aCountryCode: string) {
|
||||
}
|
||||
|
||||
export function getLocale() {
|
||||
return navigator.languages?.length
|
||||
? navigator.languages[0]
|
||||
: navigator.language ?? locale;
|
||||
return navigator.language ?? locale;
|
||||
}
|
||||
|
||||
export function getNumberFormatDecimal(aLocale?: string) {
|
||||
@ -230,7 +228,7 @@ export function getNumberFormatDecimal(aLocale?: string) {
|
||||
}).value;
|
||||
}
|
||||
|
||||
export function getNumberFormatGroup(aLocale?: string) {
|
||||
export function getNumberFormatGroup(aLocale = getLocale()) {
|
||||
const formatObject = new Intl.NumberFormat(aLocale).formatToParts(9999.99);
|
||||
|
||||
return formatObject.find((object) => {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { getLocale } from '@ghostfolio/common/helper';
|
||||
import { AccountBalancesResponse } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import {
|
||||
@ -25,7 +26,7 @@ import { Subject } from 'rxjs';
|
||||
export class AccountBalancesComponent implements OnChanges, OnDestroy, OnInit {
|
||||
@Input() accountBalances: AccountBalancesResponse['balances'];
|
||||
@Input() accountId: string;
|
||||
@Input() locale: string;
|
||||
@Input() locale = getLocale();
|
||||
@Input() showActions = true;
|
||||
|
||||
@Output() accountBalanceDeleted = new EventEmitter<string>();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
|
||||
import { getDateFormatString } from '@ghostfolio/common/helper';
|
||||
import { getDateFormatString, getLocale } from '@ghostfolio/common/helper';
|
||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||
import { OrderWithAccount } from '@ghostfolio/common/types';
|
||||
|
||||
@ -40,7 +40,7 @@ export class ActivitiesTableComponent
|
||||
@Input() hasPermissionToCreateActivity: boolean;
|
||||
@Input() hasPermissionToExportActivities: boolean;
|
||||
@Input() hasPermissionToOpenDetails = true;
|
||||
@Input() locale: string;
|
||||
@Input() locale = getLocale();
|
||||
@Input() pageIndex: number;
|
||||
@Input() pageSize = DEFAULT_PAGE_SIZE;
|
||||
@Input() showActions = true;
|
||||
|
@ -4,12 +4,13 @@
|
||||
[queryParams]="queryParams"
|
||||
[routerLink]="routerLink"
|
||||
(click)="onClick()"
|
||||
><span><b>{{ item?.name }}</b></span>
|
||||
><span
|
||||
><b>{{ item?.name }}</b></span
|
||||
>
|
||||
<br />
|
||||
<small class="text-muted"
|
||||
>{{ item?.symbol | gfSymbol }} · {{ item?.currency }}<ng-container
|
||||
*ngIf="item?.assetSubClassString"
|
||||
>
|
||||
>{{ item?.symbol | gfSymbol }} · {{ item?.currency
|
||||
}}<ng-container *ngIf="item?.assetSubClassString">
|
||||
· {{ item?.assetSubClassString }}</ng-container
|
||||
></small
|
||||
></a
|
||||
|
@ -39,7 +39,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="isLoading || searchFormControl.value"
|
||||
*ngIf="isLoading || searchFormControl.value"
|
||||
class="overflow-auto py-3 result-container"
|
||||
>
|
||||
<div>
|
||||
@ -56,9 +56,9 @@
|
||||
animation="pulse"
|
||||
class="mx-2"
|
||||
[theme]="{
|
||||
height: '1.5rem',
|
||||
width: '100%'
|
||||
}"
|
||||
height: '1.5rem',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
<div *ngIf="!isLoading" class="px-2 py-1" i18n>No entries...</div>
|
||||
</ng-container>
|
||||
@ -77,9 +77,9 @@
|
||||
animation="pulse"
|
||||
class="mx-2"
|
||||
[theme]="{
|
||||
height: '1.5rem',
|
||||
width: '100%'
|
||||
}"
|
||||
height: '1.5rem',
|
||||
width: '100%'
|
||||
}"
|
||||
/>
|
||||
<div *ngIf="!isLoading" class="px-2 py-1" i18n>No entries...</div>
|
||||
</ng-container>
|
||||
@ -87,7 +87,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<form [formGroup]="filterForm">
|
||||
<ng-container *ngIf="!(isLoading || searchFormControl.value)">
|
||||
<ng-container *ngIf="!(isLoading || searchFormControl.value)">
|
||||
<div class="date-range-selector-container p-3">
|
||||
<mat-form-field appearance="outline" class="w-100 without-hint">
|
||||
<mat-label i18n>Date Range</mat-label>
|
||||
@ -96,7 +96,7 @@
|
||||
(selectionChange)="onChangeDateRange($event.value)"
|
||||
>
|
||||
@for (range of dateRangeOptions; track range) {
|
||||
<mat-option [value]="range.value">{{ range.label }}</mat-option>
|
||||
<mat-option [value]="range.value">{{ range.label }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
@ -108,16 +108,16 @@
|
||||
<mat-select formControlName="account">
|
||||
<mat-option [value]="null" />
|
||||
@for (account of accounts; track account.id) {
|
||||
<mat-option [value]="account.id">
|
||||
<div class="d-flex">
|
||||
<gf-symbol-icon
|
||||
*ngIf="account.Platform?.url"
|
||||
class="mr-1"
|
||||
[tooltip]="account.Platform?.name"
|
||||
[url]="account.Platform?.url"
|
||||
/><span>{{ account.name }}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
<mat-option [value]="account.id">
|
||||
<div class="d-flex">
|
||||
<gf-symbol-icon
|
||||
*ngIf="account.Platform?.url"
|
||||
class="mr-1"
|
||||
[tooltip]="account.Platform?.name"
|
||||
[url]="account.Platform?.url"
|
||||
/><span>{{ account.name }}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
@ -128,7 +128,7 @@
|
||||
<mat-select formControlName="tag">
|
||||
<mat-option [value]="null" />
|
||||
@for (tag of tags; track tag.id) {
|
||||
<mat-option [value]="tag.id">{{ tag.label }}</mat-option>
|
||||
<mat-option [value]="tag.id">{{ tag.label }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
@ -139,9 +139,9 @@
|
||||
<mat-select formControlName="assetClass">
|
||||
<mat-option [value]="null" />
|
||||
@for (assetClass of assetClasses; track assetClass.id) {
|
||||
<mat-option [value]="assetClass.id"
|
||||
>{{ assetClass.label }}</mat-option
|
||||
>
|
||||
<mat-option [value]="assetClass.id">{{
|
||||
assetClass.label
|
||||
}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { resolveMarketCondition } from '@ghostfolio/common/helper';
|
||||
import { getLocale, resolveMarketCondition } from '@ghostfolio/common/helper';
|
||||
import { Benchmark, User } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import {
|
||||
@ -16,7 +16,7 @@ import {
|
||||
})
|
||||
export class BenchmarkComponent implements OnChanges {
|
||||
@Input() benchmarks: Benchmark[];
|
||||
@Input() locale: string;
|
||||
@Input() locale = getLocale();
|
||||
@Input() user: User;
|
||||
|
||||
public displayedColumns = ['name', 'date', 'change', 'marketCondition'];
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
transformTickToAbbreviation
|
||||
} from '@ghostfolio/common/chart-helper';
|
||||
import { primaryColorRgb } from '@ghostfolio/common/config';
|
||||
import { getLocale } from '@ghostfolio/common/helper';
|
||||
import { ColorScheme } from '@ghostfolio/common/types';
|
||||
|
||||
import {
|
||||
@ -55,7 +56,7 @@ export class FireCalculatorComponent implements OnChanges, OnDestroy {
|
||||
@Input() deviceType: string;
|
||||
@Input() fireWealth: number;
|
||||
@Input() hasPermissionToUpdateUserSettings: boolean;
|
||||
@Input() locale: string;
|
||||
@Input() locale = getLocale();
|
||||
@Input() projectedTotalAmount = 0;
|
||||
@Input() retirementDate: Date;
|
||||
@Input() savingsRate = 0;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { getLocale } from '@ghostfolio/common/helper';
|
||||
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import {
|
||||
@ -29,7 +30,7 @@ export class HoldingsTableComponent implements OnChanges, OnDestroy, OnInit {
|
||||
@Input() hasPermissionToOpenDetails = true;
|
||||
@Input() hasPermissionToShowValues = true;
|
||||
@Input() holdings: PortfolioPosition[];
|
||||
@Input() locale: string;
|
||||
@Input() locale = getLocale();
|
||||
@Input() pageSize = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
|
@ -3,14 +3,11 @@ import {
|
||||
getTooltipPositionerMapTop,
|
||||
getVerticalHoverLinePlugin
|
||||
} from '@ghostfolio/common/chart-helper';
|
||||
import {
|
||||
locale,
|
||||
primaryColorRgb,
|
||||
secondaryColorRgb
|
||||
} from '@ghostfolio/common/config';
|
||||
import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config';
|
||||
import {
|
||||
getBackgroundColor,
|
||||
getDateFormatString,
|
||||
getLocale,
|
||||
getTextColor
|
||||
} from '@ghostfolio/common/helper';
|
||||
import { LineChartItem } from '@ghostfolio/common/interfaces';
|
||||
@ -51,7 +48,7 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
|
||||
@Input() currency: string;
|
||||
@Input() historicalDataItems: LineChartItem[];
|
||||
@Input() isAnimated = false;
|
||||
@Input() locale: string;
|
||||
@Input() locale = getLocale();
|
||||
@Input() showGradient = false;
|
||||
@Input() showLegend = false;
|
||||
@Input() showLoader = true;
|
||||
@ -106,10 +103,6 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { getTooltipOptions } from '@ghostfolio/common/chart-helper';
|
||||
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
||||
import { getTextColor } from '@ghostfolio/common/helper';
|
||||
import { getLocale, getTextColor } from '@ghostfolio/common/helper';
|
||||
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||
import { ColorScheme } from '@ghostfolio/common/types';
|
||||
import { translate } from '@ghostfolio/ui/i18n';
|
||||
@ -41,7 +41,7 @@ export class PortfolioProportionChartComponent
|
||||
@Input() cursor: string;
|
||||
@Input() isInPercent = false;
|
||||
@Input() keys: string[] = [];
|
||||
@Input() locale = '';
|
||||
@Input() locale = getLocale();
|
||||
@Input() maxItems?: number;
|
||||
@Input() showLabels = false;
|
||||
@Input() positions: {
|
||||
|
@ -21,7 +21,7 @@ export class ValueComponent implements OnChanges {
|
||||
@Input() isCurrency = false;
|
||||
@Input() isDate = false;
|
||||
@Input() isPercent = false;
|
||||
@Input() locale: string | undefined;
|
||||
@Input() locale = getLocale();
|
||||
@Input() position = '';
|
||||
@Input() precision: number | undefined;
|
||||
@Input() size: 'large' | 'medium' | 'small' = 'small';
|
||||
@ -129,11 +129,6 @@ export class ValueComponent implements OnChanges {
|
||||
this.formattedValue = '';
|
||||
this.isNumber = false;
|
||||
this.isString = false;
|
||||
|
||||
if (!this.locale) {
|
||||
this.locale = getLocale();
|
||||
}
|
||||
|
||||
this.useAbsoluteValue = false;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ghostfolio",
|
||||
"version": "2.53.1",
|
||||
"version": "2.55.0",
|
||||
"homepage": "https://ghostfol.io",
|
||||
"license": "AGPL-3.0",
|
||||
"repository": "https://github.com/ghostfolio/ghostfolio",
|
||||
@ -25,7 +25,7 @@
|
||||
"database:push": "prisma db push",
|
||||
"database:seed": "prisma db seed",
|
||||
"database:setup": "yarn database:push && yarn database:seed",
|
||||
"database:validate": "prisma validate",
|
||||
"database:validate-schema": "prisma validate",
|
||||
"dep-graph": "nx dep-graph",
|
||||
"e2e": "ng e2e",
|
||||
"extract-locales": "nx run client:extract-i18n --output-path ./apps/client/src/locales",
|
||||
|
@ -0,0 +1,11 @@
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Account_id_idx" ON "Account"("id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "MarketData_dataSource_idx" ON "MarketData"("dataSource");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "MarketData_date_idx" ON "MarketData"("date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Order_accountId_idx" ON "Order"("accountId");
|
@ -0,0 +1,86 @@
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Access_alias_idx" ON "Access"("alias");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Access_granteeUserId_idx" ON "Access"("granteeUserId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Access_userId_idx" ON "Access"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Account_currency_idx" ON "Account"("currency");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Account_name_idx" ON "Account"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Account_userId_idx" ON "Account"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AccountBalance_accountId_idx" ON "AccountBalance"("accountId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AccountBalance_date_idx" ON "AccountBalance"("date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Analytics_updatedAt_idx" ON "Analytics"("updatedAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AuthDevice_userId_idx" ON "AuthDevice"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "MarketData_marketPrice_idx" ON "MarketData"("marketPrice");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "MarketData_state_idx" ON "MarketData"("state");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Order_date_idx" ON "Order"("date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Order_isDraft_idx" ON "Order"("isDraft");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Order_userId_idx" ON "Order"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Platform_name_idx" ON "Platform"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Subscription_userId_idx" ON "Subscription"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SymbolProfile_assetClass_idx" ON "SymbolProfile"("assetClass");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SymbolProfile_currency_idx" ON "SymbolProfile"("currency");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SymbolProfile_dataSource_idx" ON "SymbolProfile"("dataSource");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SymbolProfile_isin_idx" ON "SymbolProfile"("isin");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SymbolProfile_name_idx" ON "SymbolProfile"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SymbolProfile_symbol_idx" ON "SymbolProfile"("symbol");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Tag_name_idx" ON "Tag"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "User_accessToken_idx" ON "User"("accessToken");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "User_createdAt_idx" ON "User"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "User_provider_idx" ON "User"("provider");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "User_role_idx" ON "User"("role");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "User_thirdPartyId_idx" ON "User"("thirdPartyId");
|
@ -19,6 +19,10 @@ model Access {
|
||||
userId String
|
||||
GranteeUser User? @relation("accessGet", fields: [granteeUserId], references: [id])
|
||||
User User @relation("accessGive", fields: [userId], references: [id])
|
||||
|
||||
@@index([alias])
|
||||
@@index([granteeUserId])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model Account {
|
||||
@ -39,6 +43,10 @@ model Account {
|
||||
Order Order[]
|
||||
|
||||
@@id([id, userId])
|
||||
@@index([currency])
|
||||
@@index([id])
|
||||
@@index([name])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model AccountBalance {
|
||||
@ -50,6 +58,9 @@ model AccountBalance {
|
||||
userId String
|
||||
value Float
|
||||
Account Account @relation(fields: [accountId, userId], onDelete: Cascade, references: [id, userId])
|
||||
|
||||
@@index([accountId])
|
||||
@@index([date])
|
||||
}
|
||||
|
||||
model Analytics {
|
||||
@ -58,6 +69,8 @@ model Analytics {
|
||||
updatedAt DateTime @updatedAt
|
||||
userId String @id
|
||||
User User @relation(fields: [userId], references: [id])
|
||||
|
||||
@@index([updatedAt])
|
||||
}
|
||||
|
||||
model AuthDevice {
|
||||
@ -69,6 +82,8 @@ model AuthDevice {
|
||||
updatedAt DateTime @updatedAt
|
||||
userId String
|
||||
User User @relation(fields: [userId], references: [id])
|
||||
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model MarketData {
|
||||
@ -81,6 +96,10 @@ model MarketData {
|
||||
symbol String
|
||||
|
||||
@@unique([dataSource, date, symbol])
|
||||
@@index([dataSource])
|
||||
@@index([date])
|
||||
@@index([marketPrice])
|
||||
@@index([state])
|
||||
@@index([symbol])
|
||||
}
|
||||
|
||||
@ -103,6 +122,11 @@ model Order {
|
||||
SymbolProfile SymbolProfile @relation(fields: [symbolProfileId], references: [id])
|
||||
User User @relation(fields: [userId], references: [id])
|
||||
tags Tag[]
|
||||
|
||||
@@index([accountId])
|
||||
@@index([date])
|
||||
@@index([isDraft])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model Platform {
|
||||
@ -110,6 +134,8 @@ model Platform {
|
||||
name String?
|
||||
url String @unique
|
||||
Account Account[]
|
||||
|
||||
@@index([name])
|
||||
}
|
||||
|
||||
model Property {
|
||||
@ -148,6 +174,12 @@ model SymbolProfile {
|
||||
SymbolProfileOverrides SymbolProfileOverrides?
|
||||
|
||||
@@unique([dataSource, symbol])
|
||||
@@index([assetClass])
|
||||
@@index([currency])
|
||||
@@index([dataSource])
|
||||
@@index([isin])
|
||||
@@index([name])
|
||||
@@index([symbol])
|
||||
}
|
||||
|
||||
model SymbolProfileOverrides {
|
||||
@ -170,12 +202,16 @@ model Subscription {
|
||||
updatedAt DateTime @updatedAt
|
||||
userId String
|
||||
User User @relation(fields: [userId], references: [id])
|
||||
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model Tag {
|
||||
id String @id @default(uuid())
|
||||
name String @unique
|
||||
orders Order[]
|
||||
|
||||
@@index([name])
|
||||
}
|
||||
|
||||
model User {
|
||||
@ -195,6 +231,12 @@ model User {
|
||||
Order Order[]
|
||||
Settings Settings?
|
||||
Subscription Subscription[]
|
||||
|
||||
@@index([accessToken])
|
||||
@@index([createdAt])
|
||||
@@index([provider])
|
||||
@@index([role])
|
||||
@@index([thirdPartyId])
|
||||
}
|
||||
|
||||
enum AccessPermission {
|
||||
|
Reference in New Issue
Block a user