Feature/refactor holding detail dialog to standalone (#3407)

* Refactor holding detail dialog to standalone

* Update changelog
pull/3409/head^2
Thomas Kaul 6 months ago committed by GitHub
parent 812ff5cbdc
commit e31b4c64cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Refactored the holding detail dialog to a standalone component
- Refreshed the cryptocurrencies list - Refreshed the cryptocurrencies list
- Refactored various pages to standalone components - Refactored various pages to standalone components
- Upgraded `body-parser` from version `1.20.1` to `1.20.2` - Upgraded `body-parser` from version `1.20.1` to `1.20.2`

@ -7,7 +7,7 @@ import {
import { Account, Tag } from '@prisma/client'; import { Account, Tag } from '@prisma/client';
export interface PortfolioPositionDetail { export interface PortfolioHoldingDetail {
accounts: Account[]; accounts: Account[];
averagePrice: number; averagePrice: number;
dataProviderInfo: DataProviderInfo; dataProviderInfo: DataProviderInfo;

@ -51,7 +51,7 @@ import { AssetClass, AssetSubClass } from '@prisma/client';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { PortfolioPositionDetail } from './interfaces/portfolio-position-detail.interface'; import { PortfolioHoldingDetail } from './interfaces/portfolio-holding-detail.interface';
import { PortfolioService } from './portfolio.service'; import { PortfolioService } from './portfolio.service';
@Controller('portfolio') @Controller('portfolio')
@ -569,7 +569,7 @@ export class PortfolioController {
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
@Param('dataSource') dataSource, @Param('dataSource') dataSource,
@Param('symbol') symbol @Param('symbol') symbol
): Promise<PortfolioPositionDetail> { ): Promise<PortfolioHoldingDetail> {
const position = await this.portfolioService.getPosition( const position = await this.portfolioService.getPosition(
dataSource, dataSource,
impersonationId, impersonationId,

@ -77,7 +77,7 @@ import {
PerformanceCalculationType, PerformanceCalculationType,
PortfolioCalculatorFactory PortfolioCalculatorFactory
} from './calculator/portfolio-calculator.factory'; } from './calculator/portfolio-calculator.factory';
import { PortfolioPositionDetail } from './interfaces/portfolio-position-detail.interface'; import { PortfolioHoldingDetail } from './interfaces/portfolio-holding-detail.interface';
import { RulesService } from './rules.service'; import { RulesService } from './rules.service';
const asiaPacificMarkets = require('../../assets/countries/asia-pacific-markets.json'); const asiaPacificMarkets = require('../../assets/countries/asia-pacific-markets.json');
@ -602,7 +602,7 @@ export class PortfolioService {
aDataSource: DataSource, aDataSource: DataSource,
aImpersonationId: string, aImpersonationId: string,
aSymbol: string aSymbol: string
): Promise<PortfolioPositionDetail> { ): Promise<PortfolioHoldingDetail> {
const userId = await this.getUserId(aImpersonationId, this.request.user.id); const userId = await this.getUserId(aImpersonationId, this.request.user.id);
const user = await this.userService.user({ id: userId }); const user = await this.userService.user({ id: userId });
const userCurrency = this.getUserCurrency(user); const userCurrency = this.getUserCurrency(user);
@ -693,7 +693,7 @@ export class PortfolioService {
transactionCount transactionCount
} = position; } = position;
const accounts: PortfolioPositionDetail['accounts'] = uniqBy( const accounts: PortfolioHoldingDetail['accounts'] = uniqBy(
orders.filter(({ Account }) => { orders.filter(({ Account }) => {
return Account; return Account;
}), }),

@ -1,3 +1,5 @@
import { GfHoldingDetailDialogComponent } from '@ghostfolio/client/components/holding-detail-dialog/holding-detail-dialog.component';
import { HoldingDetailDialogParams } from '@ghostfolio/client/components/holding-detail-dialog/interfaces/interfaces';
import { getCssVariable } from '@ghostfolio/common/helper'; import { getCssVariable } from '@ghostfolio/common/helper';
import { InfoItem, User } from '@ghostfolio/common/interfaces'; import { InfoItem, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
@ -26,8 +28,6 @@ import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators'; import { filter, takeUntil } from 'rxjs/operators';
import { PositionDetailDialogParams } from './components/position-detail-dialog/interfaces/interfaces';
import { PositionDetailDialog } from './components/position-detail-dialog/position-detail-dialog.component';
import { DataService } from './services/data.service'; import { DataService } from './services/data.service';
import { ImpersonationStorageService } from './services/impersonation-storage.service'; import { ImpersonationStorageService } from './services/impersonation-storage.service';
import { TokenStorageService } from './services/token-storage.service'; import { TokenStorageService } from './services/token-storage.service';
@ -246,9 +246,9 @@ export class AppComponent implements OnDestroy, OnInit {
.subscribe((user) => { .subscribe((user) => {
this.user = user; this.user = user;
const dialogRef = this.dialog.open(PositionDetailDialog, { const dialogRef = this.dialog.open(GfHoldingDetailDialogComponent, {
autoFocus: false, autoFocus: false,
data: <PositionDetailDialogParams>{ data: <HoldingDetailDialogParams>{
dataSource, dataSource,
symbol, symbol,
baseCurrency: this.user?.settings?.baseCurrency, baseCurrency: this.user?.settings?.baseCurrency,

@ -1,4 +1,7 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { GfAccountsTableModule } from '@ghostfolio/client/components/accounts-table/accounts-table.module';
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
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 { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper'; import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper';
@ -8,9 +11,16 @@ import {
LineChartItem, LineChartItem,
User User
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table';
import { GfDataProviderCreditsComponent } from '@ghostfolio/ui/data-provider-credits';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
import { GfLineChartComponent } from '@ghostfolio/ui/line-chart';
import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart';
import { GfValueComponent } from '@ghostfolio/ui/value';
import { CommonModule } from '@angular/common';
import { import {
CUSTOM_ELEMENTS_SCHEMA,
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
@ -18,24 +28,50 @@ import {
OnDestroy, OnDestroy,
OnInit OnInit
} from '@angular/core'; } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatButtonModule } from '@angular/material/button';
import { MatChipsModule } from '@angular/material/chips';
import {
MAT_DIALOG_DATA,
MatDialogModule,
MatDialogRef
} from '@angular/material/dialog';
import { SortDirection } from '@angular/material/sort'; import { SortDirection } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { Account, Tag } from '@prisma/client'; import { Account, Tag } from '@prisma/client';
import { format, isSameMonth, isToday, parseISO } from 'date-fns'; import { format, isSameMonth, isToday, parseISO } from 'date-fns';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { PositionDetailDialogParams } from './interfaces/interfaces'; import { HoldingDetailDialogParams } from './interfaces/interfaces';
@Component({ @Component({
host: { class: 'd-flex flex-column h-100' },
selector: 'gf-position-detail-dialog',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: 'position-detail-dialog.html', host: { class: 'd-flex flex-column h-100' },
styleUrls: ['./position-detail-dialog.component.scss'] imports: [
CommonModule,
GfAccountsTableModule,
GfActivitiesTableComponent,
GfDataProviderCreditsComponent,
GfDialogFooterModule,
GfDialogHeaderModule,
GfLineChartComponent,
GfPortfolioProportionChartComponent,
GfValueComponent,
MatButtonModule,
MatChipsModule,
MatDialogModule,
MatTabsModule,
NgxSkeletonLoaderModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-holding-detail-dialog',
standalone: true,
styleUrls: ['./holding-detail-dialog.component.scss'],
templateUrl: 'holding-detail-dialog.html'
}) })
export class PositionDetailDialog implements OnDestroy, OnInit { export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
public accounts: Account[]; public accounts: Account[];
public activities: Activity[]; public activities: Activity[];
public assetClass: string; public assetClass: string;
@ -80,14 +116,14 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
public constructor( public constructor(
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService, private dataService: DataService,
public dialogRef: MatDialogRef<PositionDetailDialog>, public dialogRef: MatDialogRef<GfHoldingDetailDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: PositionDetailDialogParams, @Inject(MAT_DIALOG_DATA) public data: HoldingDetailDialogParams,
private userService: UserService private userService: UserService
) {} ) {}
public ngOnInit() { public ngOnInit() {
this.dataService this.dataService
.fetchPositionDetail({ .fetchHoldingDetail({
dataSource: this.data.dataSource, dataSource: this.data.dataSource,
symbol: this.data.symbol symbol: this.data.symbol
}) })

@ -2,7 +2,7 @@ import { ColorScheme } from '@ghostfolio/common/types';
import { DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
export interface PositionDetailDialogParams { export interface HoldingDetailDialogParams {
baseCurrency: string; baseCurrency: string;
colorScheme: ColorScheme; colorScheme: ColorScheme;
dataSource: DataSource; dataSource: DataSource;

@ -1,40 +0,0 @@
import { GfAccountsTableModule } from '@ghostfolio/client/components/accounts-table/accounts-table.module';
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table';
import { GfDataProviderCreditsComponent } from '@ghostfolio/ui/data-provider-credits';
import { GfLineChartComponent } from '@ghostfolio/ui/line-chart';
import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart';
import { GfValueComponent } from '@ghostfolio/ui/value';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatChipsModule } from '@angular/material/chips';
import { MatDialogModule } from '@angular/material/dialog';
import { MatTabsModule } from '@angular/material/tabs';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { PositionDetailDialog } from './position-detail-dialog.component';
@NgModule({
declarations: [PositionDetailDialog],
imports: [
CommonModule,
GfAccountsTableModule,
GfActivitiesTableComponent,
GfDataProviderCreditsComponent,
GfDialogFooterModule,
GfDialogHeaderModule,
GfLineChartComponent,
GfPortfolioProportionChartComponent,
GfValueComponent,
MatButtonModule,
MatChipsModule,
MatDialogModule,
MatTabsModule,
NgxSkeletonLoaderModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfPositionDetailDialogModule {}

@ -6,7 +6,7 @@ import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { Activities } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activities } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
import { PortfolioPositionDetail } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position-detail.interface'; import { PortfolioHoldingDetail } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-holding-detail.interface';
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface'; import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface';
import { UserItem } from '@ghostfolio/api/app/user/interfaces/user-item.interface'; import { UserItem } from '@ghostfolio/api/app/user/interfaces/user-item.interface';
@ -325,6 +325,31 @@ export class DataService {
}); });
} }
public fetchHoldingDetail({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
return this.http
.get<PortfolioHoldingDetail>(
`/api/v1/portfolio/position/${dataSource}/${symbol}`
)
.pipe(
map((data) => {
if (data.orders) {
for (const order of data.orders) {
order.createdAt = parseISO(<string>(<unknown>order.createdAt));
order.date = parseISO(<string>(<unknown>order.date));
}
}
return data;
})
);
}
public fetchInfo(): InfoItem { public fetchInfo(): InfoItem {
const info = cloneDeep((window as any).info); const info = cloneDeep((window as any).info);
const utmSource = <'ios' | 'trusted-web-activity'>( const utmSource = <'ios' | 'trusted-web-activity'>(
@ -563,31 +588,6 @@ export class DataService {
return this.http.get<PortfolioReport>('/api/v1/portfolio/report'); return this.http.get<PortfolioReport>('/api/v1/portfolio/report');
} }
public fetchPositionDetail({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
return this.http
.get<PortfolioPositionDetail>(
`/api/v1/portfolio/position/${dataSource}/${symbol}`
)
.pipe(
map((data) => {
if (data.orders) {
for (const order of data.orders) {
order.createdAt = parseISO(<string>(<unknown>order.createdAt));
order.date = parseISO(<string>(<unknown>order.date));
}
}
return data;
})
);
}
public loginAnonymous(accessToken: string) { public loginAnonymous(accessToken: string) {
return this.http.post<OAuthResponse>(`/api/v1/auth/anonymous`, { return this.http.post<OAuthResponse>(`/api/v1/auth/anonymous`, {
accessToken accessToken

@ -1,5 +1,5 @@
import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset-profile-icon/asset-profile-icon.component'; import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset-profile-icon/asset-profile-icon.component';
import { GfPositionDetailDialogModule } from '@ghostfolio/client/components/position-detail-dialog/position-detail-dialog.module'; import { GfHoldingDetailDialogComponent } from '@ghostfolio/client/components/holding-detail-dialog/holding-detail-dialog.component';
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
import { getLocale } from '@ghostfolio/common/helper'; import { getLocale } from '@ghostfolio/common/helper';
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces'; import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
@ -23,7 +23,7 @@ import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { Router, RouterModule } from '@angular/router'; import { Router, RouterModule } from '@angular/router';
import { AssetClass, AssetSubClass } from '@prisma/client'; import { AssetSubClass } from '@prisma/client';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { Subject, Subscription } from 'rxjs'; import { Subject, Subscription } from 'rxjs';
@ -32,8 +32,8 @@ import { Subject, Subscription } from 'rxjs';
imports: [ imports: [
CommonModule, CommonModule,
GfAssetProfileIconComponent, GfAssetProfileIconComponent,
GfHoldingDetailDialogComponent,
GfNoTransactionsInfoComponent, GfNoTransactionsInfoComponent,
GfPositionDetailDialogModule,
GfSymbolModule, GfSymbolModule,
GfValueComponent, GfValueComponent,
MatButtonModule, MatButtonModule,

Loading…
Cancel
Save