diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index 538ebb62b..a6c0fde7f 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -3,17 +3,26 @@ import { ChangeDetectorRef, Component, OnDestroy, - OnInit + OnInit, + ViewChild } 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 { DataService } from '@ghostfolio/client/services/data.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 { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface'; import { DataSource, MarketData } from '@prisma/client'; +import { format, parseISO } from 'date-fns'; +import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { AssetProfileDialog } from './asset-profile-dialog/asset-profile-dialog.component'; +import { AssetProfileDialogParams } from './asset-profile-dialog/interfaces/interfaces'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -22,11 +31,22 @@ import { takeUntil } from 'rxjs/operators'; templateUrl: './admin-market-data.html' }) export class AdminMarketDataComponent implements OnDestroy, OnInit { + @ViewChild(MatSort) sort: MatSort; + public currentDataSource: DataSource; public currentSymbol: string; + public dataSource: MatTableDataSource = new MatTableDataSource(); public defaultDateFormat: string; + public deviceType: string; + public displayedColumns = [ + 'symbol', + 'dataSource', + 'date', + 'activityCount', + 'marketDataItemCount', + 'actions' + ]; public marketData: AdminMarketDataItem[] = []; - public marketDataDetails: MarketData[] = []; public user: User; private unsubscribeSubject = new Subject(); @@ -35,8 +55,29 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit { private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private deviceService: DeviceDetectorService, + private dialog: MatDialog, + private route: ActivatedRoute, + private router: Router, 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 .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((state) => { @@ -51,6 +92,8 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit { } public ngOnInit() { + this.deviceType = this.deviceService.getDeviceInfo().deviceType; + this.fetchAdminMarketData(); } @@ -75,28 +118,19 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit { .subscribe(() => {}); } - public onMarketDataChanged(withRefresh: boolean = false) { - if (withRefresh) { - this.fetchAdminMarketData(); - this.fetchAdminMarketDataBySymbol({ - dataSource: this.currentDataSource, - symbol: this.currentSymbol - }); - } - } - - public setCurrentProfile({ dataSource, symbol }: UniqueAsset) { - this.marketDataDetails = []; - - if (this.currentSymbol === symbol) { - this.currentDataSource = undefined; - this.currentSymbol = ''; - } else { - this.currentDataSource = dataSource; - this.currentSymbol = symbol; - - this.fetchAdminMarketDataBySymbol({ dataSource, symbol }); - } + public onOpenAssetProfileDialog({ + dataSource, + dateOfFirstActivity, + symbol + }: UniqueAsset & { dateOfFirstActivity: string }) { + this.router.navigate([], { + queryParams: { + dataSource, + symbol, + assetProfileDialog: true, + dateOfFirstActivity: format(parseISO(dateOfFirstActivity), DATE_FORMAT) + } + }); } public ngOnDestroy() { @@ -109,20 +143,48 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit { .fetchAdminMarketData() .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ marketData }) => { - this.marketData = marketData; + this.dataSource = new MatTableDataSource(marketData); + + this.dataSource.sort = this.sort; this.changeDetectorRef.markForCheck(); }); } - private fetchAdminMarketDataBySymbol({ dataSource, symbol }: UniqueAsset) { - this.adminService - .fetchAdminMarketDataBySymbol({ dataSource, symbol }) + private openAssetProfileDialog({ + dataSource, + dateOfFirstActivity, + symbol + }: { + dataSource: DataSource; + dateOfFirstActivity: string; + symbol: string; + }) { + this.userService + .get() .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ marketData }) => { - this.marketDataDetails = marketData; - - this.changeDetectorRef.markForCheck(); + .subscribe((user) => { + this.user = user; + + const dialogRef = this.dialog.open(AssetProfileDialog, { + autoFocus: false, + data: { + dataSource, + dateOfFirstActivity, + symbol, + deviceType: this.deviceType, + locale: this.user?.settings?.locale + }, + height: this.deviceType === 'mobile' ? '97.5vh' : '80vh', + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); + + dialogRef + .afterClosed() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(() => { + this.router.navigate(['.'], { relativeTo: this.route }); + }); }); } } diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.html b/apps/client/src/app/components/admin-market-data/admin-market-data.html index 654a692a6..4b160ca9a 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.html +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -1,76 +1,101 @@
- - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + +
SymbolData SourceFirst ActivityActivity CountHistorical Data
+ Symbol + + {{ element.symbol }} + + Data Source + + {{ element.dataSource }} + + First Activity + + {{ (element.date | date: defaultDateFormat) ?? '' }} + + Activity Count + + {{ element.activityCount }} + + Historical Data + + {{ element.marketDataItemCount }} + + {{ item.symbol }}{{ item.dataSource }} - {{ (item.date | date: defaultDateFormat) ?? '' }} - {{ item.activityCount }}{{ item.marketDataItemCount }} - - - - - - -
- -
diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.module.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.module.ts index 1a3432fff..64e7225d7 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.module.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.module.ts @@ -2,17 +2,21 @@ import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; 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 { AdminMarketDataComponent } from './admin-market-data.component'; +import { GfAssetProfileDialogModule } from './asset-profile-dialog/assset-profile-dialog.module'; @NgModule({ declarations: [AdminMarketDataComponent], imports: [ CommonModule, - GfAdminMarketDataDetailModule, + GfAssetProfileDialogModule, MatButtonModule, - MatMenuModule + MatMenuModule, + MatSortModule, + MatTableModule ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.scss b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.scss new file mode 100644 index 000000000..ce1c7d599 --- /dev/null +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.scss @@ -0,0 +1,7 @@ +:host { + display: block; + + .mat-dialog-content { + max-height: unset; + } +} diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts new file mode 100644 index 000000000..3d718cd96 --- /dev/null +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -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(); + + public constructor( + private adminService: AdminService, + private changeDetectorRef: ChangeDetectorRef, + public dialogRef: MatDialogRef, + @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 + }); + } +} diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html new file mode 100644 index 000000000..f5f7a632d --- /dev/null +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -0,0 +1,24 @@ + + +
+ +
+ + diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/assset-profile-dialog.module.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/assset-profile-dialog.module.ts new file mode 100644 index 000000000..966883daa --- /dev/null +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/assset-profile-dialog.module.ts @@ -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 { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module'; +import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module'; +import { GfAdminMarketDataDetailModule } from '../../admin-market-data-detail/admin-market-data-detail.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 {} diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts new file mode 100644 index 000000000..05c70f749 --- /dev/null +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts @@ -0,0 +1,9 @@ +import { DataSource } from '@prisma/client'; + +export interface AssetProfileDialogParams { + dateOfFirstActivity: string; + dataSource: DataSource; + deviceType: string; + locale: string; + symbol: string; +} diff --git a/apps/client/src/app/components/home-holdings/home-holdings.component.ts b/apps/client/src/app/components/home-holdings/home-holdings.component.ts index 2065da3f1..fff2529d4 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.component.ts +++ b/apps/client/src/app/components/home-holdings/home-holdings.component.ts @@ -47,7 +47,7 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit { private settingsStorageService: SettingsStorageService, private userService: UserService ) { - route.queryParams + this.route.queryParams .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((params) => { if (