Compare commits

..

5 Commits

Author SHA1 Message Date
7c8530483c Release 1.183.0 (#1189) 2022-08-24 20:55:38 +02:00
539d3ff754 Feature/add asset sub class filter (#1188)
* Add asset sub class filter

* Update changelog
2022-08-24 20:53:50 +02:00
9d28b63da6 Release 1.182.0 (#1186) 2022-08-23 21:40:57 +02:00
24abbd85e6 Feature/move asset profile details to dialog (#1185)
* Introduce asset profile dialog

* Update changelog
2022-08-23 21:39:04 +02:00
b6f395fd3b Feature/improve i18n (#1183)
* Improve i18n

* Update changelog
2022-08-22 19:57:48 +02:00
26 changed files with 814 additions and 173 deletions

View File

@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 1.183.0 - 24.08.2022
### Added
- Added a filter by asset sub class for the asset profiles in the admin control
### Changed
- Improved the language localization for German (`de`)
## 1.182.0 - 23.08.2022
### Changed
- Improved the language localization for German (`de`)
- Extended and made the columns of the asset profiles sortable in the admin control
- Moved the asset profile details in the admin control panel to a dialog
## 1.181.2 - 21.08.2022 ## 1.181.2 - 21.08.2022
### Added ### Added

View File

@ -8,7 +8,8 @@ import {
import { import {
AdminData, AdminData,
AdminMarketData, AdminMarketData,
AdminMarketDataDetails AdminMarketDataDetails,
Filter
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types'; import type { RequestWithUser } from '@ghostfolio/common/types';
@ -22,6 +23,7 @@ import {
Param, Param,
Post, Post,
Put, Put,
Query,
UseGuards UseGuards
} from '@nestjs/common'; } from '@nestjs/common';
import { REQUEST } from '@nestjs/core'; import { REQUEST } from '@nestjs/core';
@ -226,7 +228,9 @@ export class AdminController {
@Get('market-data') @Get('market-data')
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard('jwt'))
public async getMarketData(): Promise<AdminMarketData> { public async getMarketData(
@Query('assetSubClasses') filterByAssetSubClasses?: string
): Promise<AdminMarketData> {
if ( if (
!hasPermission( !hasPermission(
this.request.user.permissions, this.request.user.permissions,
@ -239,7 +243,18 @@ export class AdminController {
); );
} }
return this.adminService.getMarketData(); const assetSubClasses = filterByAssetSubClasses?.split(',') ?? [];
const filters: Filter[] = [
...assetSubClasses.map((assetSubClass) => {
return <Filter>{
id: assetSubClass,
type: 'ASSET_SUB_CLASS'
};
})
];
return this.adminService.getMarketData(filters);
} }
@Get('market-data/:dataSource/:symbol') @Get('market-data/:dataSource/:symbol')

View File

@ -11,11 +11,13 @@ import {
AdminMarketData, AdminMarketData,
AdminMarketDataDetails, AdminMarketDataDetails,
AdminMarketDataItem, AdminMarketDataItem,
Filter,
UniqueAsset UniqueAsset
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Property } from '@prisma/client'; import { AssetSubClass, Prisma, Property } from '@prisma/client';
import { differenceInDays } from 'date-fns'; import { differenceInDays } from 'date-fns';
import { groupBy } from 'lodash';
@Injectable() @Injectable()
export class AdminService { export class AdminService {
@ -63,14 +65,27 @@ export class AdminService {
}; };
} }
public async getMarketData(): Promise<AdminMarketData> { public async getMarketData(filters?: Filter[]): Promise<AdminMarketData> {
const where: Prisma.SymbolProfileWhereInput = {};
const { ASSET_SUB_CLASS: filtersByAssetSubClass } = groupBy(
filters,
(filter) => {
return filter.type;
}
);
const marketData = await this.prismaService.marketData.groupBy({ const marketData = await this.prismaService.marketData.groupBy({
_count: true, _count: true,
by: ['dataSource', 'symbol'] by: ['dataSource', 'symbol']
}); });
const currencyPairsToGather: AdminMarketDataItem[] = let currencyPairsToGather: AdminMarketDataItem[] = [];
this.exchangeRateDataService
if (filtersByAssetSubClass) {
where.assetSubClass = AssetSubClass[filtersByAssetSubClass[0].id];
} else {
currencyPairsToGather = this.exchangeRateDataService
.getCurrencyPairs() .getCurrencyPairs()
.map(({ dataSource, symbol }) => { .map(({ dataSource, symbol }) => {
const marketDataItemCount = const marketDataItemCount =
@ -84,17 +99,24 @@ export class AdminService {
return { return {
dataSource, dataSource,
marketDataItemCount, marketDataItemCount,
symbol symbol,
countriesCount: 0,
sectorsCount: 0
}; };
}); });
}
const symbolProfilesToGather: AdminMarketDataItem[] = ( const symbolProfilesToGather: AdminMarketDataItem[] = (
await this.prismaService.symbolProfile.findMany({ await this.prismaService.symbolProfile.findMany({
where,
orderBy: [{ symbol: 'asc' }], orderBy: [{ symbol: 'asc' }],
select: { select: {
_count: { _count: {
select: { Order: true } select: { Order: true }
}, },
assetClass: true,
assetSubClass: true,
countries: true,
dataSource: true, dataSource: true,
Order: { Order: {
orderBy: [{ date: 'asc' }], orderBy: [{ date: 'asc' }],
@ -102,10 +124,14 @@ export class AdminService {
take: 1 take: 1
}, },
scraperConfiguration: true, scraperConfiguration: true,
sectors: true,
symbol: true symbol: true
} }
}) })
).map((symbolProfile) => { ).map((symbolProfile) => {
const countriesCount = symbolProfile.countries
? Object.keys(symbolProfile.countries).length
: 0;
const marketDataItemCount = const marketDataItemCount =
marketData.find((marketDataItem) => { marketData.find((marketDataItem) => {
return ( return (
@ -113,10 +139,17 @@ export class AdminService {
marketDataItem.symbol === symbolProfile.symbol marketDataItem.symbol === symbolProfile.symbol
); );
})?._count ?? 0; })?._count ?? 0;
const sectorsCount = symbolProfile.sectors
? Object.keys(symbolProfile.sectors).length
: 0;
return { return {
countriesCount,
marketDataItemCount, marketDataItemCount,
sectorsCount,
activityCount: symbolProfile._count.Order, activityCount: symbolProfile._count.Order,
assetClass: symbolProfile.assetClass,
assetSubClass: symbolProfile.assetSubClass,
dataSource: symbolProfile.dataSource, dataSource: symbolProfile.dataSource,
date: symbolProfile.Order?.[0]?.date, date: symbolProfile.Order?.[0]?.date,
symbol: symbolProfile.symbol symbol: symbolProfile.symbol

View File

@ -3,17 +3,27 @@ import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
OnDestroy, OnDestroy,
OnInit OnInit,
ViewChild
} from '@angular/core'; } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router';
import { AdminService } from '@ghostfolio/client/services/admin.service'; import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { getDateFormatString } from '@ghostfolio/common/helper'; import { DATE_FORMAT, getDateFormatString } from '@ghostfolio/common/helper';
import { UniqueAsset, User } from '@ghostfolio/common/interfaces'; import { Filter, UniqueAsset, User } from '@ghostfolio/common/interfaces';
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface'; import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
import { DataSource, MarketData } from '@prisma/client'; import { AssetSubClass, DataSource } from '@prisma/client';
import { format, parseISO } from 'date-fns';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators';
import { AssetProfileDialog } from './asset-profile-dialog/asset-profile-dialog.component';
import { AssetProfileDialogParams } from './asset-profile-dialog/interfaces/interfaces';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@ -22,11 +32,46 @@ import { takeUntil } from 'rxjs/operators';
templateUrl: './admin-market-data.html' templateUrl: './admin-market-data.html'
}) })
export class AdminMarketDataComponent implements OnDestroy, OnInit { export class AdminMarketDataComponent implements OnDestroy, OnInit {
@ViewChild(MatSort) sort: MatSort;
public activeFilters: Filter[] = [];
public allFilters: Filter[] = [
AssetSubClass.BOND,
AssetSubClass.COMMODITY,
AssetSubClass.CRYPTOCURRENCY,
AssetSubClass.ETF,
AssetSubClass.MUTUALFUND,
AssetSubClass.PRECIOUS_METAL,
AssetSubClass.PRIVATE_EQUITY,
AssetSubClass.STOCK
].map((id) => {
return {
id,
label: id,
type: 'ASSET_SUB_CLASS'
};
});
public currentDataSource: DataSource; public currentDataSource: DataSource;
public currentSymbol: string; public currentSymbol: string;
public dataSource: MatTableDataSource<AdminMarketDataItem> =
new MatTableDataSource();
public defaultDateFormat: string; public defaultDateFormat: string;
public marketData: AdminMarketDataItem[] = []; public deviceType: string;
public marketDataDetails: MarketData[] = []; public displayedColumns = [
'symbol',
'dataSource',
'assetClass',
'assetSubClass',
'date',
'activityCount',
'marketDataItemCount',
'countriesCount',
'sectorsCount',
'actions'
];
public filters$ = new Subject<Filter[]>();
public isLoading = false;
public placeholder = '';
public user: User; public user: User;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -35,8 +80,29 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
private adminService: AdminService, private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService, private dataService: DataService,
private deviceService: DeviceDetectorService,
private dialog: MatDialog,
private route: ActivatedRoute,
private router: Router,
private userService: UserService private userService: UserService
) { ) {
this.route.queryParams
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((params) => {
if (
params['assetProfileDialog'] &&
params['dataSource'] &&
params['dateOfFirstActivity'] &&
params['symbol']
) {
this.openAssetProfileDialog({
dataSource: params['dataSource'],
dateOfFirstActivity: params['dateOfFirstActivity'],
symbol: params['symbol']
});
}
});
this.userService.stateChanged this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => { .subscribe((state) => {
@ -51,7 +117,31 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
} }
public ngOnInit() { public ngOnInit() {
this.fetchAdminMarketData(); this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.filters$
.pipe(
distinctUntilChanged(),
switchMap((filters) => {
this.isLoading = true;
this.activeFilters = filters;
this.placeholder =
this.activeFilters.length <= 0 ? $localize`Filter by...` : '';
return this.dataService.fetchAdminMarketData({
filters: this.activeFilters
});
}),
takeUntil(this.unsubscribeSubject)
)
.subscribe(({ marketData }) => {
this.dataSource = new MatTableDataSource(marketData);
this.dataSource.sort = this.sort;
this.isLoading = false;
this.changeDetectorRef.markForCheck();
});
} }
public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) { public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) {
@ -75,28 +165,19 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
.subscribe(() => {}); .subscribe(() => {});
} }
public onMarketDataChanged(withRefresh: boolean = false) { public onOpenAssetProfileDialog({
if (withRefresh) { dataSource,
this.fetchAdminMarketData(); dateOfFirstActivity,
this.fetchAdminMarketDataBySymbol({ symbol
dataSource: this.currentDataSource, }: UniqueAsset & { dateOfFirstActivity: string }) {
symbol: this.currentSymbol this.router.navigate([], {
}); queryParams: {
} dataSource,
} symbol,
assetProfileDialog: true,
public setCurrentProfile({ dataSource, symbol }: UniqueAsset) { dateOfFirstActivity: format(parseISO(dateOfFirstActivity), DATE_FORMAT)
this.marketDataDetails = []; }
});
if (this.currentSymbol === symbol) {
this.currentDataSource = undefined;
this.currentSymbol = '';
} else {
this.currentDataSource = dataSource;
this.currentSymbol = symbol;
this.fetchAdminMarketDataBySymbol({ dataSource, symbol });
}
} }
public ngOnDestroy() { public ngOnDestroy() {
@ -104,25 +185,40 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private fetchAdminMarketData() { private openAssetProfileDialog({
this.dataService dataSource,
.fetchAdminMarketData() dateOfFirstActivity,
symbol
}: {
dataSource: DataSource;
dateOfFirstActivity: string;
symbol: string;
}) {
this.userService
.get()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ marketData }) => { .subscribe((user) => {
this.marketData = marketData; this.user = user;
this.changeDetectorRef.markForCheck(); const dialogRef = this.dialog.open(AssetProfileDialog, {
}); autoFocus: false,
} data: <AssetProfileDialogParams>{
dataSource,
dateOfFirstActivity,
symbol,
deviceType: this.deviceType,
locale: this.user?.settings?.locale
},
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
});
private fetchAdminMarketDataBySymbol({ dataSource, symbol }: UniqueAsset) { dialogRef
this.adminService .afterClosed()
.fetchAdminMarketDataBySymbol({ dataSource, symbol }) .pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntil(this.unsubscribeSubject)) .subscribe(() => {
.subscribe(({ marketData }) => { this.router.navigate(['.'], { relativeTo: this.route });
this.marketDataDetails = marketData; });
this.changeDetectorRef.markForCheck();
}); });
} }
} }

View File

@ -1,76 +1,147 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<table class="gf-table w-100"> <gf-activities-filter
<thead> [allFilters]="allFilters"
<tr class="mat-header-row"> [isLoading]="isLoading"
<th class="mat-header-cell px-1 py-2" i18n>Symbol</th> [placeholder]="placeholder"
<th class="mat-header-cell px-1 py-2" i18n>Data Source</th> (valueChanged)="filters$.next($event)"
<th class="mat-header-cell px-1 py-2" i18n>First Activity</th> ></gf-activities-filter>
<th class="mat-header-cell px-1 py-2" i18n>Activity Count</th> </div>
<th class="mat-header-cell px-1 py-2" i18n>Historical Data</th> </div>
<th class="mat-header-cell px-1 py-2"></th> <div class="row">
</tr> <div class="col">
</thead> <table
<tbody> class="gf-table w-100"
<ng-container *ngFor="let item of marketData; let i = index"> matSort
<tr matSortActive="symbol"
class="cursor-pointer mat-row" matSortDirection="asc"
(click)="setCurrentProfile({ dataSource: item.dataSource, symbol: item.symbol })" mat-table
[dataSource]="dataSource"
>
<ng-container matColumnDef="symbol">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
<ng-container i18n>Symbol</ng-container>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
{{ element.symbol }}
</td>
</ng-container>
<ng-container matColumnDef="dataSource">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
<ng-container i18n>Data Source</ng-container>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
{{ element.dataSource }}
</td>
</ng-container>
<ng-container matColumnDef="assetClass">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
<ng-container i18n>Asset Class</ng-container>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
{{ element.assetClass }}
</td>
</ng-container>
<ng-container matColumnDef="assetSubClass">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
<ng-container i18n>Asset Sub Class</ng-container>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
{{ element.assetSubClass }}
</td>
</ng-container>
<ng-container matColumnDef="date">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
<ng-container i18n>First Activity</ng-container>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
{{ (element.date | date: defaultDateFormat) ?? '' }}
</td>
</ng-container>
<ng-container matColumnDef="activityCount">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
<ng-container i18n>Activity Count</ng-container>
</th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
{{ element.activityCount }}
</td>
</ng-container>
<ng-container matColumnDef="marketDataItemCount">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
<ng-container i18n>Historical Data</ng-container>
</th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
{{ element.marketDataItemCount }}
</td>
</ng-container>
<ng-container matColumnDef="countriesCount">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
<ng-container i18n>Countries Count</ng-container>
</th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
{{ element.countriesCount }}
</td>
</ng-container>
<ng-container matColumnDef="sectorsCount">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
<ng-container i18n>Sectors Count</ng-container>
</th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
{{ element.sectorsCount }}
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th *matHeaderCellDef class="px-1 text-center" mat-header-cell></th>
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
<button
class="mx-1 no-min-width px-2"
mat-button
[matMenuTriggerFor]="accountMenu"
(click)="$event.stopPropagation()"
> >
<td class="mat-cell px-1 py-2">{{ item.symbol }}</td> <ion-icon name="ellipsis-vertical"></ion-icon>
<td class="mat-cell px-1 py-2">{{ item.dataSource }}</td> </button>
<td class="mat-cell px-1 py-2"> <mat-menu #accountMenu="matMenu" xPosition="before">
{{ (item.date | date: defaultDateFormat) ?? '' }} <button
</td> mat-menu-item
<td class="mat-cell px-1 py-2">{{ item.activityCount }}</td> (click)="onGatherSymbol({dataSource: element.dataSource, symbol: element.symbol})"
<td class="mat-cell px-1 py-2">{{ item.marketDataItemCount }}</td> >
<td class="mat-cell px-1 py-2"> <ng-container i18n>Gather Data</ng-container>
<button </button>
class="mx-1 no-min-width px-2" <button
mat-button mat-menu-item
[matMenuTriggerFor]="accountMenu" (click)="onGatherProfileDataBySymbol({dataSource: element.dataSource, symbol: element.symbol})"
(click)="$event.stopPropagation()" >
> <ng-container i18n>Gather Profile Data</ng-container>
<ion-icon name="ellipsis-vertical"></ion-icon> </button>
</button> <button
<mat-menu #accountMenu="matMenu" xPosition="before"> mat-menu-item
<button [disabled]="element.activityCount !== 0"
mat-menu-item (click)="onDeleteProfileData({dataSource: element.dataSource, symbol: element.symbol})"
(click)="onGatherSymbol({dataSource: item.dataSource, symbol: item.symbol})" >
> <ng-container i18n>Delete</ng-container>
<ng-container i18n>Gather Data</ng-container> </button>
</button> </mat-menu>
<button </td>
mat-menu-item </ng-container>
(click)="onGatherProfileDataBySymbol({dataSource: item.dataSource, symbol: item.symbol})"
> <tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
<ng-container i18n>Gather Profile Data</ng-container> <tr
</button> *matRowDef="let row; columns: displayedColumns"
<button class="cursor-pointer"
mat-menu-item mat-row
[disabled]="item.activityCount !== 0" (click)="onOpenAssetProfileDialog({ dateOfFirstActivity: row.date, dataSource: row.dataSource, symbol: row.symbol })"
(click)="onDeleteProfileData({dataSource: item.dataSource, symbol: item.symbol})" ></tr>
>
<ng-container i18n>Delete</ng-container>
</button>
</mat-menu>
</td>
</tr>
<tr *ngIf="currentSymbol === item.symbol" class="mat-row">
<td class="p-1" colspan="6">
<gf-admin-market-data-detail
[dataSource]="item.dataSource"
[dateOfFirstActivity]="item.date"
[locale]="user?.settings?.locale"
[marketData]="marketDataDetails"
[symbol]="item.symbol"
(marketDataChanged)="onMarketDataChanged($event)"
></gf-admin-market-data-detail>
</td>
</tr>
</ng-container>
</tbody>
</table> </table>
</div> </div>
</div> </div>

View File

@ -2,17 +2,23 @@ import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { GfAdminMarketDataDetailModule } from '@ghostfolio/client/components/admin-market-data-detail/admin-market-data-detail.module'; import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module';
import { AdminMarketDataComponent } from './admin-market-data.component'; import { AdminMarketDataComponent } from './admin-market-data.component';
import { GfAssetProfileDialogModule } from './asset-profile-dialog/assset-profile-dialog.module';
@NgModule({ @NgModule({
declarations: [AdminMarketDataComponent], declarations: [AdminMarketDataComponent],
imports: [ imports: [
CommonModule, CommonModule,
GfAdminMarketDataDetailModule, GfActivitiesFilterModule,
GfAssetProfileDialogModule,
MatButtonModule, MatButtonModule,
MatMenuModule MatMenuModule,
MatSortModule,
MatTableModule
], ],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}) })

View File

@ -0,0 +1,7 @@
:host {
display: block;
.mat-dialog-content {
max-height: unset;
}
}

View File

@ -0,0 +1,73 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Inject,
OnDestroy,
OnInit
} from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { MarketData } from '@prisma/client';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { AssetProfileDialogParams } from './interfaces/interfaces';
@Component({
host: { class: 'd-flex flex-column h-100' },
selector: 'gf-asset-profile-dialog',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: 'asset-profile-dialog.html',
styleUrls: ['./asset-profile-dialog.component.scss']
})
export class AssetProfileDialog implements OnDestroy, OnInit {
public marketDataDetails: MarketData[] = [];
private unsubscribeSubject = new Subject<void>();
public constructor(
private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef,
public dialogRef: MatDialogRef<AssetProfileDialog>,
@Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogParams
) {}
public ngOnInit(): void {
this.initialize();
}
public onClose(): void {
this.dialogRef.close();
}
public onMarketDataChanged(withRefresh: boolean = false) {
if (withRefresh) {
this.initialize();
}
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private fetchAdminMarketDataBySymbol({ dataSource, symbol }: UniqueAsset) {
this.adminService
.fetchAdminMarketDataBySymbol({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ marketData }) => {
this.marketDataDetails = marketData;
this.changeDetectorRef.markForCheck();
});
}
private initialize() {
this.fetchAdminMarketDataBySymbol({
dataSource: this.data.dataSource,
symbol: this.data.symbol
});
}
}

View File

@ -0,0 +1,24 @@
<gf-dialog-header
mat-dialog-title
position="center"
[deviceType]="data.deviceType"
[title]="data.symbol"
(closeButtonClicked)="onClose()"
></gf-dialog-header>
<div class="flex-grow-1" mat-dialog-content>
<gf-admin-market-data-detail
[dataSource]="data.dataSource"
[dateOfFirstActivity]="data.dateOfFirstActivity"
[locale]="data.locale"
[marketData]="marketDataDetails"
[symbol]="data.symbol"
(marketDataChanged)="onMarketDataChanged($event)"
></gf-admin-market-data-detail>
</div>
<gf-dialog-footer
mat-dialog-actions
[deviceType]="data.deviceType"
(closeButtonClicked)="onClose()"
></gf-dialog-footer>

View File

@ -0,0 +1,23 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { GfAdminMarketDataDetailModule } from '@ghostfolio/client/components/admin-market-data-detail/admin-market-data-detail.module';
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
import { AssetProfileDialog } from './asset-profile-dialog.component';
@NgModule({
declarations: [AssetProfileDialog],
imports: [
CommonModule,
GfAdminMarketDataDetailModule,
GfDialogFooterModule,
GfDialogHeaderModule,
MatButtonModule,
MatDialogModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfAssetProfileDialogModule {}

View File

@ -0,0 +1,9 @@
import { DataSource } from '@prisma/client';
export interface AssetProfileDialogParams {
dateOfFirstActivity: string;
dataSource: DataSource;
deviceType: string;
locale: string;
symbol: string;
}

View File

@ -47,7 +47,7 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
private settingsStorageService: SettingsStorageService, private settingsStorageService: SettingsStorageService,
private userService: UserService private userService: UserService
) { ) {
route.queryParams this.route.queryParams
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((params) => { .subscribe((params) => {
if ( if (

View File

@ -21,6 +21,8 @@ import { takeUntil } from 'rxjs/operators';
export class HomeMarketComponent implements OnDestroy, OnInit { export class HomeMarketComponent implements OnDestroy, OnInit {
public benchmarks: Benchmark[]; public benchmarks: Benchmark[];
public fearAndGreedIndex: number; public fearAndGreedIndex: number;
public fearLabel = $localize`Fear`;
public greedLabel = $localize`Greed`;
public hasPermissionToAccessFearAndGreedIndex: boolean; public hasPermissionToAccessFearAndGreedIndex: boolean;
public historicalData: HistoricalDataItem[]; public historicalData: HistoricalDataItem[];
public info: InfoItem; public info: InfoItem;

View File

@ -9,13 +9,13 @@
class="mb-3" class="mb-3"
symbol="Fear & Greed Index" symbol="Fear & Greed Index"
yMax="100" yMax="100"
yMaxLabel="Greed"
yMin="0" yMin="0"
yMinLabel="Fear"
[historicalDataItems]="historicalData" [historicalDataItems]="historicalData"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[showXAxis]="true" [showXAxis]="true"
[showYAxis]="true" [showYAxis]="true"
[yMaxLabel]="greedLabel"
[yMinLabel]="fearLabel"
></gf-line-chart> ></gf-line-chart>
<gf-fear-and-greed-index <gf-fear-and-greed-index
class="d-flex justify-content-center" class="d-flex justify-content-center"

View File

@ -122,7 +122,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
data: this.investments.map((position) => { data: this.investments.map((position) => {
return position.investment; return position.investment;
}), }),
label: 'Investment', label: $localize`Deposit`,
segment: { segment: {
borderColor: (context: unknown) => borderColor: (context: unknown) =>
this.isInFuture( this.isInFuture(

View File

@ -54,8 +54,8 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
}; };
public period = 'current'; public period = 'current';
public periodOptions: ToggleOption[] = [ public periodOptions: ToggleOption[] = [
{ label: 'Initial', value: 'original' }, { label: $localize`Initial`, value: 'original' },
{ label: 'Current', value: 'current' } { label: $localize`Current`, value: 'current' }
]; ];
public placeholder = ''; public placeholder = '';
public portfolioDetails: PortfolioDetails; public portfolioDetails: PortfolioDetails;
@ -85,7 +85,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
public user: User; public user: User;
private readonly SEARCH_PLACEHOLDER = 'Filter by account or tag...';
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
public constructor( public constructor(
@ -133,7 +132,9 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
this.isLoading = true; this.isLoading = true;
this.activeFilters = filters; this.activeFilters = filters;
this.placeholder = this.placeholder =
this.activeFilters.length <= 0 ? this.SEARCH_PLACEHOLDER : ''; this.activeFilters.length <= 0
? $localize`Filter by account or tag...`
: '';
return this.dataService.fetchPortfolioDetails({ return this.dataService.fetchPortfolioDetails({
filters: this.activeFilters filters: this.activeFilters

View File

@ -26,8 +26,8 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
public investmentsByMonth: InvestmentItem[]; public investmentsByMonth: InvestmentItem[];
public mode: GroupBy; public mode: GroupBy;
public modeOptions: ToggleOption[] = [ public modeOptions: ToggleOption[] = [
{ label: 'Monthly', value: 'month' }, { label: $localize`Monthly`, value: 'month' },
{ label: 'Accumulating', value: undefined } { label: $localize`Accumulating`, value: undefined }
]; ];
public top3: Position[]; public top3: Position[];
public user: User; public user: User;

View File

@ -52,7 +52,7 @@
<mat-card class="mb-3"> <mat-card class="mb-3">
<mat-card-header> <mat-card-header>
<mat-card-title class="align-items-center d-flex" i18n <mat-card-title class="align-items-center d-flex" i18n
>Top 3</mat-card-title >Top</mat-card-title
> >
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>
@ -88,7 +88,7 @@
<mat-card class="mb-3"> <mat-card class="mb-3">
<mat-card-header> <mat-card-header>
<mat-card-title class="align-items-center d-flex" i18n <mat-card-title class="align-items-center d-flex" i18n
>Bottom 3</mat-card-title >Bottom</mat-card-title
> >
</mat-card-header> </mat-card-header>
<mat-card-content> <mat-card-content>

View File

@ -133,8 +133,32 @@ export class DataService {
return this.http.get<AdminData>('/api/v1/admin'); return this.http.get<AdminData>('/api/v1/admin');
} }
public fetchAdminMarketData() { public fetchAdminMarketData({ filters }: { filters?: Filter[] }) {
return this.http.get<AdminMarketData>('/api/v1/admin/market-data'); let params = new HttpParams();
if (filters?.length > 0) {
const { ASSET_SUB_CLASS: filtersByAssetSubClass } = groupBy(
filters,
(filter) => {
return filter.type;
}
);
if (filtersByAssetSubClass) {
params = params.append(
'assetSubClasses',
filtersByAssetSubClass
.map(({ id }) => {
return id;
})
.join(',')
);
}
}
return this.http.get<AdminMarketData>('/api/v1/admin/market-data', {
params
});
} }
public deleteAccess(aId: string) { public deleteAccess(aId: string) {

View File

@ -186,7 +186,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">55</context> <context context-type="linenumber">132</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-users/admin-users.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-users/admin-users.html</context>
@ -222,7 +222,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">7</context> <context context-type="linenumber">24</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/positions-table/positions-table.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/positions-table/positions-table.component.html</context>
@ -242,7 +242,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">8</context> <context context-type="linenumber">33</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
@ -406,7 +406,7 @@
<target state="translated">Erste Aktivität</target> <target state="translated">Erste Aktivität</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">9</context> <context context-type="linenumber">60</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ced0954194f098201837bb03b32441e4991b5193" datatype="html"> <trans-unit id="ced0954194f098201837bb03b32441e4991b5193" datatype="html">
@ -414,7 +414,7 @@
<target state="translated">Anzahl Aktivitäten</target> <target state="translated">Anzahl Aktivitäten</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">10</context> <context context-type="linenumber">69</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
@ -426,7 +426,7 @@
<target state="translated">Historische Daten</target> <target state="translated">Historische Daten</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">11</context> <context context-type="linenumber">78</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="f835caf68bff562ddd23556a651e834d5af3380b" datatype="html"> <trans-unit id="f835caf68bff562ddd23556a651e834d5af3380b" datatype="html">
@ -434,7 +434,7 @@
<target state="translated">Daten einholen</target> <target state="translated">Daten einholen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">42</context> <context context-type="linenumber">119</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="912825160188860007" datatype="html"> <trans-unit id="912825160188860007" datatype="html">
@ -514,7 +514,7 @@
<target state="translated">Profildaten einholen</target> <target state="translated">Profildaten einholen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">125</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
@ -1709,16 +1709,16 @@
<context context-type="linenumber">10</context> <context context-type="linenumber">10</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="50167734b0d4b546842e9f548eb89480a72d0fa1" datatype="html"> <trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html">
<source>Top 3</source> <source>Top</source>
<target state="translated">Gewinner</target> <target state="translated">Gewinner</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">55</context> <context context-type="linenumber">55</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="d4fb5bf78e78dd3c791bcec0dcddddb1fdc9d51a" datatype="html"> <trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
<source>Bottom 3</source> <source>Bottom</source>
<target state="translated">Verlierer</target> <target state="translated">Verlierer</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
@ -1988,6 +1988,10 @@
<trans-unit id="584c9433705e9bfdd2e7a9f0192690f453d36196" datatype="html"> <trans-unit id="584c9433705e9bfdd2e7a9f0192690f453d36196" datatype="html">
<source>Asset Class</source> <source>Asset Class</source>
<target state="translated">Anlageklasse</target> <target state="translated">Anlageklasse</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">42</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">145</context> <context context-type="linenumber">145</context>
@ -2380,6 +2384,10 @@
<trans-unit id="27fe3d097c64eaec7ff564358f80fb7ba795f484" datatype="html"> <trans-unit id="27fe3d097c64eaec7ff564358f80fb7ba795f484" datatype="html">
<source>Asset Sub Class</source> <source>Asset Sub Class</source>
<target state="translated">Anlageunterklasse</target> <target state="translated">Anlageunterklasse</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">51</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">154</context> <context context-type="linenumber">154</context>
@ -2493,6 +2501,122 @@
<context context-type="linenumber">171</context> <context context-type="linenumber">171</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="e34e2478d2d30c9d01758d01b7212411171b9bd5" datatype="html">
<source>Projected Total Amount</source>
<target state="translated">Geschätzter Gesamtbetrag</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.html</context>
<context context-type="linenumber">44</context>
</context-group>
</trans-unit>
<trans-unit id="2937311350146031865" datatype="html">
<source>Initial</source>
<target state="new">Beginn</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
<context context-type="linenumber">57</context>
</context-group>
</trans-unit>
<trans-unit id="6603000223840533819" datatype="html">
<source>Current</source>
<target state="translated">Aktuell</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
<context context-type="linenumber">58</context>
</context-group>
</trans-unit>
<trans-unit id="6762743264882388498" datatype="html">
<source>Monthly</source>
<target state="translated">Monatlich</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">29</context>
</context-group>
</trans-unit>
<trans-unit id="1975246224413290232" datatype="html">
<source>Accumulating</source>
<target state="translated">Aufsummiert</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="5213771062241898526" datatype="html">
<source>Deposit</source>
<target state="translated">Einlage</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context>
<context context-type="linenumber">125</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
<context context-type="linenumber">276</context>
</context-group>
</trans-unit>
<trans-unit id="3441715041566940420" datatype="html">
<source>Interest</source>
<target state="translated">Verzinsung</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
<context context-type="linenumber">286</context>
</context-group>
</trans-unit>
<trans-unit id="1054498214311181686" datatype="html">
<source>Savings</source>
<target state="new">Ersparnisse</target>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
<context context-type="linenumber">296</context>
</context-group>
</trans-unit>
<trans-unit id="aad5320acd7453f912bc8714e72c2fa71e8ab18e" datatype="html">
<source>Countries Count</source>
<target state="translated">Anzahl Länder</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">87</context>
</context-group>
</trans-unit>
<trans-unit id="8511b16abcf065252b350d64e337ba2447db3ffb" datatype="html">
<source>Sectors Count</source>
<target state="translated">Anzahl Sektoren</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">96</context>
</context-group>
</trans-unit>
<trans-unit id="5486880308148746399" datatype="html">
<source>Fear</source>
<target state="translated">Angst</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/home-market/home-market.component.ts</context>
<context context-type="linenumber">24</context>
</context-group>
</trans-unit>
<trans-unit id="6844699413925472826" datatype="html">
<source>Greed</source>
<target state="translated">Gier</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/home-market/home-market.component.ts</context>
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit id="4550487415324294802" datatype="html">
<source>Filter by...</source>
<target state="translated">Filtern nach...</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">129</context>
</context-group>
</trans-unit>
<trans-unit id="2078421919111943467" datatype="html">
<source>Filter by account or tag...</source>
<target state="translated">Filtern nach Konto oder Tag...</target>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
<context context-type="linenumber">136</context>
</context-group>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -174,7 +174,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">55</context> <context context-type="linenumber">132</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-users/admin-users.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-users/admin-users.html</context>
@ -207,7 +207,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">7</context> <context context-type="linenumber">24</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/positions-table/positions-table.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/positions-table/positions-table.component.html</context>
@ -226,7 +226,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">8</context> <context context-type="linenumber">33</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/transactions/create-or-update-transaction-dialog/create-or-update-transaction-dialog.html</context>
@ -375,14 +375,14 @@
<source>First Activity</source> <source>First Activity</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">9</context> <context context-type="linenumber">60</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="ced0954194f098201837bb03b32441e4991b5193" datatype="html"> <trans-unit id="ced0954194f098201837bb03b32441e4991b5193" datatype="html">
<source>Activity Count</source> <source>Activity Count</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">10</context> <context context-type="linenumber">69</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
@ -393,14 +393,14 @@
<source>Historical Data</source> <source>Historical Data</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">11</context> <context context-type="linenumber">78</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="f835caf68bff562ddd23556a651e834d5af3380b" datatype="html"> <trans-unit id="f835caf68bff562ddd23556a651e834d5af3380b" datatype="html">
<source>Gather Data</source> <source>Gather Data</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">42</context> <context context-type="linenumber">119</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="912825160188860007" datatype="html"> <trans-unit id="912825160188860007" datatype="html">
@ -470,7 +470,7 @@
<source>Gather Profile Data</source> <source>Gather Profile Data</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">125</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context> <context context-type="sourcefile">apps/client/src/app/components/admin-overview/admin-overview.html</context>
@ -1534,15 +1534,15 @@
<context context-type="linenumber">10</context> <context context-type="linenumber">10</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="50167734b0d4b546842e9f548eb89480a72d0fa1" datatype="html"> <trans-unit id="6ae1c94f6bad274424f97e9bc8766242c1577447" datatype="html">
<source>Top 3</source> <source>Top</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">55</context> <context context-type="linenumber">55</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="d4fb5bf78e78dd3c791bcec0dcddddb1fdc9d51a" datatype="html"> <trans-unit id="6723d5c967329a3ac75524cf0c1af5ced022b9a3" datatype="html">
<source>Bottom 3</source> <source>Bottom</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.html</context>
<context context-type="linenumber">91</context> <context context-type="linenumber">91</context>
@ -1781,6 +1781,10 @@
</trans-unit> </trans-unit>
<trans-unit id="584c9433705e9bfdd2e7a9f0192690f453d36196" datatype="html"> <trans-unit id="584c9433705e9bfdd2e7a9f0192690f453d36196" datatype="html">
<source>Asset Class</source> <source>Asset Class</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">42</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">145</context> <context context-type="linenumber">145</context>
@ -2108,6 +2112,10 @@
</trans-unit> </trans-unit>
<trans-unit id="27fe3d097c64eaec7ff564358f80fb7ba795f484" datatype="html"> <trans-unit id="27fe3d097c64eaec7ff564358f80fb7ba795f484" datatype="html">
<source>Asset Sub Class</source> <source>Asset Sub Class</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">51</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context> <context context-type="sourcefile">apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html</context>
<context context-type="linenumber">154</context> <context context-type="linenumber">154</context>
@ -2227,6 +2235,108 @@
<context context-type="linenumber">145</context> <context context-type="linenumber">145</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="e34e2478d2d30c9d01758d01b7212411171b9bd5" datatype="html">
<source>Projected Total Amount</source>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.html</context>
<context context-type="linenumber">44</context>
</context-group>
</trans-unit>
<trans-unit id="1054498214311181686" datatype="html">
<source>Savings</source>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
<context context-type="linenumber">296</context>
</context-group>
</trans-unit>
<trans-unit id="1975246224413290232" datatype="html">
<source>Accumulating</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="2937311350146031865" datatype="html">
<source>Initial</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
<context context-type="linenumber">57</context>
</context-group>
</trans-unit>
<trans-unit id="3441715041566940420" datatype="html">
<source>Interest</source>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
<context context-type="linenumber">286</context>
</context-group>
</trans-unit>
<trans-unit id="5213771062241898526" datatype="html">
<source>Deposit</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/investment-chart/investment-chart.component.ts</context>
<context context-type="linenumber">125</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">libs/ui/src/lib/fire-calculator/fire-calculator.component.ts</context>
<context context-type="linenumber">276</context>
</context-group>
</trans-unit>
<trans-unit id="6603000223840533819" datatype="html">
<source>Current</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
<context context-type="linenumber">58</context>
</context-group>
</trans-unit>
<trans-unit id="6762743264882388498" datatype="html">
<source>Monthly</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/analysis/analysis-page.component.ts</context>
<context context-type="linenumber">29</context>
</context-group>
</trans-unit>
<trans-unit id="8511b16abcf065252b350d64e337ba2447db3ffb" datatype="html">
<source>Sectors Count</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">96</context>
</context-group>
</trans-unit>
<trans-unit id="aad5320acd7453f912bc8714e72c2fa71e8ab18e" datatype="html">
<source>Countries Count</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.html</context>
<context context-type="linenumber">87</context>
</context-group>
</trans-unit>
<trans-unit id="5486880308148746399" datatype="html">
<source>Fear</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/home-market/home-market.component.ts</context>
<context context-type="linenumber">24</context>
</context-group>
</trans-unit>
<trans-unit id="6844699413925472826" datatype="html">
<source>Greed</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/home-market/home-market.component.ts</context>
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit id="2078421919111943467" datatype="html">
<source>Filter by account or tag...</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts</context>
<context context-type="linenumber">136</context>
</context-group>
</trans-unit>
<trans-unit id="4550487415324294802" datatype="html">
<source>Filter by...</source>
<context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/admin-market-data/admin-market-data.component.ts</context>
<context context-type="linenumber">129</context>
</context-group>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>

View File

@ -1,12 +1,16 @@
import { DataSource } from '@prisma/client'; import { AssetClass, AssetSubClass, DataSource } from '@prisma/client';
export interface AdminMarketData { export interface AdminMarketData {
marketData: AdminMarketDataItem[]; marketData: AdminMarketDataItem[];
} }
export interface AdminMarketDataItem { export interface AdminMarketDataItem {
assetClass?: AssetClass;
assetSubClass?: AssetSubClass;
countriesCount: number;
dataSource: DataSource; dataSource: DataSource;
date?: Date; date?: Date;
marketDataItemCount?: number; marketDataItemCount: number;
sectorsCount: number;
symbol: string; symbol: string;
} }

View File

@ -1,5 +1,5 @@
export interface Filter { export interface Filter {
id: string; id: string;
label?: string; label?: string;
type: 'ACCOUNT' | 'ASSET_CLASS' | 'SYMBOL' | 'TAG'; type: 'ACCOUNT' | 'ASSET_CLASS' | 'ASSET_SUB_CLASS' | 'SYMBOL' | 'TAG';
} }

View File

@ -35,13 +35,14 @@
</mat-form-field> </mat-form-field>
<gf-value <gf-value
label="Projected Total Amount" i18n
size="large" size="large"
[currency]="currency" [currency]="currency"
[isCurrency]="true" [isCurrency]="true"
[locale]="locale" [locale]="locale"
[value]="projectedTotalAmount" [value]="projectedTotalAmount"
></gf-value> >Projected Total Amount</gf-value
>
</form> </form>
</div> </div>
<div class="col-md-9 text-center"> <div class="col-md-9 text-center">

View File

@ -273,7 +273,7 @@ export class FireCalculatorComponent
const datasetDeposit = { const datasetDeposit = {
backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`, backgroundColor: `rgb(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b})`,
data: [], data: [],
label: 'Deposit' label: $localize`Deposit`
}; };
const datasetInterest = { const datasetInterest = {
@ -283,7 +283,7 @@ export class FireCalculatorComponent
.lighten(0.5) .lighten(0.5)
.hex(), .hex(),
data: [], data: [],
label: 'Interest' label: $localize`Interest`
}; };
const datasetSavings = { const datasetSavings = {
@ -293,7 +293,7 @@ export class FireCalculatorComponent
.lighten(0.25) .lighten(0.25)
.hex(), .hex(),
data: [], data: [],
label: 'Savings' label: $localize`Savings`
}; };
for (let period = 1; period <= t; period++) { for (let period = 1; period <= t; period++) {

View File

@ -1,6 +1,6 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "1.181.2", "version": "1.183.0",
"homepage": "https://ghostfol.io", "homepage": "https://ghostfol.io",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {