diff --git a/CHANGELOG.md b/CHANGELOG.md index 305f7fdc9..e2015e656 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Refactored the holding detail dialog to a standalone component - Refreshed the cryptocurrencies list - Refactored various pages to standalone components - Upgraded `body-parser` from version `1.20.1` to `1.20.2` diff --git a/apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts b/apps/api/src/app/portfolio/interfaces/portfolio-holding-detail.interface.ts similarity index 96% rename from apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts rename to apps/api/src/app/portfolio/interfaces/portfolio-holding-detail.interface.ts index a32d47e21..3ce23a3bc 100644 --- a/apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts +++ b/apps/api/src/app/portfolio/interfaces/portfolio-holding-detail.interface.ts @@ -7,7 +7,7 @@ import { import { Account, Tag } from '@prisma/client'; -export interface PortfolioPositionDetail { +export interface PortfolioHoldingDetail { accounts: Account[]; averagePrice: number; dataProviderInfo: DataProviderInfo; diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index ed3c0f174..66419c578 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -51,7 +51,7 @@ import { AssetClass, AssetSubClass } from '@prisma/client'; import { Big } from 'big.js'; 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'; @Controller('portfolio') @@ -569,7 +569,7 @@ export class PortfolioController { @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Param('dataSource') dataSource, @Param('symbol') symbol - ): Promise { + ): Promise { const position = await this.portfolioService.getPosition( dataSource, impersonationId, diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index a98887ca9..b7c7bd0ae 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -77,7 +77,7 @@ import { PerformanceCalculationType, PortfolioCalculatorFactory } 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'; const asiaPacificMarkets = require('../../assets/countries/asia-pacific-markets.json'); @@ -602,7 +602,7 @@ export class PortfolioService { aDataSource: DataSource, aImpersonationId: string, aSymbol: string - ): Promise { + ): Promise { const userId = await this.getUserId(aImpersonationId, this.request.user.id); const user = await this.userService.user({ id: userId }); const userCurrency = this.getUserCurrency(user); @@ -693,7 +693,7 @@ export class PortfolioService { transactionCount } = position; - const accounts: PortfolioPositionDetail['accounts'] = uniqBy( + const accounts: PortfolioHoldingDetail['accounts'] = uniqBy( orders.filter(({ Account }) => { return Account; }), diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index 8b6a7ddc3..08cc915bd 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -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 { InfoItem, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; @@ -26,8 +28,6 @@ import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject } from 'rxjs'; 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 { ImpersonationStorageService } from './services/impersonation-storage.service'; import { TokenStorageService } from './services/token-storage.service'; @@ -246,9 +246,9 @@ export class AppComponent implements OnDestroy, OnInit { .subscribe((user) => { this.user = user; - const dialogRef = this.dialog.open(PositionDetailDialog, { + const dialogRef = this.dialog.open(GfHoldingDetailDialogComponent, { autoFocus: false, - data: { + data: { dataSource, symbol, baseCurrency: this.user?.settings?.baseCurrency, diff --git a/apps/client/src/app/components/position-detail-dialog/position-detail-dialog.component.scss b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.scss similarity index 100% rename from apps/client/src/app/components/position-detail-dialog/position-detail-dialog.component.scss rename to apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.scss diff --git a/apps/client/src/app/components/position-detail-dialog/position-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts similarity index 82% rename from apps/client/src/app/components/position-detail-dialog/position-detail-dialog.component.ts rename to apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index 2a0303686..6ac058f7e 100644 --- a/apps/client/src/app/components/position-detail-dialog/position-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -1,4 +1,7 @@ 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 { UserService } from '@ghostfolio/client/services/user/user.service'; import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper'; @@ -8,9 +11,16 @@ import { LineChartItem, User } 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 { 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, ChangeDetectionStrategy, ChangeDetectorRef, Component, @@ -18,24 +28,50 @@ import { OnDestroy, OnInit } 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 { MatTableDataSource } from '@angular/material/table'; +import { MatTabsModule } from '@angular/material/tabs'; import { Account, Tag } from '@prisma/client'; import { format, isSameMonth, isToday, parseISO } from 'date-fns'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; -import { PositionDetailDialogParams } from './interfaces/interfaces'; +import { HoldingDetailDialogParams } from './interfaces/interfaces'; @Component({ - host: { class: 'd-flex flex-column h-100' }, - selector: 'gf-position-detail-dialog', changeDetection: ChangeDetectionStrategy.OnPush, - templateUrl: 'position-detail-dialog.html', - styleUrls: ['./position-detail-dialog.component.scss'] + host: { class: 'd-flex flex-column h-100' }, + 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 activities: Activity[]; public assetClass: string; @@ -80,14 +116,14 @@ export class PositionDetailDialog implements OnDestroy, OnInit { public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: PositionDetailDialogParams, + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: HoldingDetailDialogParams, private userService: UserService ) {} public ngOnInit() { this.dataService - .fetchPositionDetail({ + .fetchHoldingDetail({ dataSource: this.data.dataSource, symbol: this.data.symbol }) diff --git a/apps/client/src/app/components/position-detail-dialog/position-detail-dialog.html b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html similarity index 100% rename from apps/client/src/app/components/position-detail-dialog/position-detail-dialog.html rename to apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html diff --git a/apps/client/src/app/components/position-detail-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts similarity index 87% rename from apps/client/src/app/components/position-detail-dialog/interfaces/interfaces.ts rename to apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts index bbededf57..c6cfce1ee 100644 --- a/apps/client/src/app/components/position-detail-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/holding-detail-dialog/interfaces/interfaces.ts @@ -2,7 +2,7 @@ import { ColorScheme } from '@ghostfolio/common/types'; import { DataSource } from '@prisma/client'; -export interface PositionDetailDialogParams { +export interface HoldingDetailDialogParams { baseCurrency: string; colorScheme: ColorScheme; dataSource: DataSource; diff --git a/apps/client/src/app/components/position-detail-dialog/position-detail-dialog.module.ts b/apps/client/src/app/components/position-detail-dialog/position-detail-dialog.module.ts deleted file mode 100644 index 751c645e5..000000000 --- a/apps/client/src/app/components/position-detail-dialog/position-detail-dialog.module.ts +++ /dev/null @@ -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 {} diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 8e5648d9e..18f1b966d 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -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 { Activities } from '@ghostfolio/api/app/order/interfaces/activities.interface'; 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 { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-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( + `/api/v1/portfolio/position/${dataSource}/${symbol}` + ) + .pipe( + map((data) => { + if (data.orders) { + for (const order of data.orders) { + order.createdAt = parseISO((order.createdAt)); + order.date = parseISO((order.date)); + } + } + + return data; + }) + ); + } + public fetchInfo(): InfoItem { const info = cloneDeep((window as any).info); const utmSource = <'ios' | 'trusted-web-activity'>( @@ -563,31 +588,6 @@ export class DataService { return this.http.get('/api/v1/portfolio/report'); } - public fetchPositionDetail({ - dataSource, - symbol - }: { - dataSource: DataSource; - symbol: string; - }) { - return this.http - .get( - `/api/v1/portfolio/position/${dataSource}/${symbol}` - ) - .pipe( - map((data) => { - if (data.orders) { - for (const order of data.orders) { - order.createdAt = parseISO((order.createdAt)); - order.date = parseISO((order.date)); - } - } - - return data; - }) - ); - } - public loginAnonymous(accessToken: string) { return this.http.post(`/api/v1/auth/anonymous`, { accessToken diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.ts b/libs/ui/src/lib/holdings-table/holdings-table.component.ts index 6e6cab9e8..d1964b06f 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.ts +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.ts @@ -1,5 +1,5 @@ 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 { getLocale } from '@ghostfolio/common/helper'; 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 { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { Router, RouterModule } from '@angular/router'; -import { AssetClass, AssetSubClass } from '@prisma/client'; +import { AssetSubClass } from '@prisma/client'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject, Subscription } from 'rxjs'; @@ -32,8 +32,8 @@ import { Subject, Subscription } from 'rxjs'; imports: [ CommonModule, GfAssetProfileIconComponent, + GfHoldingDetailDialogComponent, GfNoTransactionsInfoComponent, - GfPositionDetailDialogModule, GfSymbolModule, GfValueComponent, MatButtonModule,