diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cf9dcb55..6c7f58d03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Added pagination to the historical market data table of the admin control panel + ## 1.284.0 - 2023-06-27 ### Added diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index 6512384ba..75b2b2bb9 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -3,6 +3,7 @@ import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/da import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { PropertyDto } from '@ghostfolio/api/services/property/property.dto'; import { + DEFAULT_PAGE_SIZE, GATHER_ASSET_PROFILE_PROCESS, GATHER_ASSET_PROFILE_PROCESS_OPTIONS } from '@ghostfolio/common/config'; @@ -32,7 +33,7 @@ import { } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; -import { DataSource, MarketData, SymbolProfile } from '@prisma/client'; +import { DataSource, MarketData, Prisma, SymbolProfile } from '@prisma/client'; import { isDate } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; @@ -247,7 +248,11 @@ export class AdminController { @Get('market-data') @UseGuards(AuthGuard('jwt')) public async getMarketData( - @Query('assetSubClasses') filterByAssetSubClasses?: string + @Query('assetSubClasses') filterByAssetSubClasses?: string, + @Query('skip') skip?: number, + @Query('sortColumn') sortColumn?: string, + @Query('sortDirection') sortDirection?: Prisma.SortOrder, + @Query('take') take?: number ): Promise { if ( !hasPermission( @@ -272,7 +277,13 @@ export class AdminController { }) ]; - return this.adminService.getMarketData(filters); + return this.adminService.getMarketData({ + filters, + sortColumn, + sortDirection, + skip: isNaN(skip) ? undefined : skip, + take: isNaN(take) ? undefined : take + }); } @Get('market-data/:dataSource/:symbol') diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 9a6f1bf17..861cca019 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -6,12 +6,14 @@ import { MarketDataService } from '@ghostfolio/api/services/market-data/market-d import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; -import { PROPERTY_CURRENCIES } from '@ghostfolio/common/config'; +import { + DEFAULT_PAGE_SIZE, + PROPERTY_CURRENCIES +} from '@ghostfolio/common/config'; import { AdminData, AdminMarketData, AdminMarketDataDetails, - AdminMarketDataItem, Filter, UniqueAsset } from '@ghostfolio/common/interfaces'; @@ -99,7 +101,21 @@ export class AdminService { }; } - public async getMarketData(filters?: Filter[]): Promise { + public async getMarketData({ + filters, + sortColumn, + sortDirection, + skip, + take = DEFAULT_PAGE_SIZE + }: { + filters?: Filter[]; + skip?: number; + sortColumn?: string; + sortDirection?: Prisma.SortOrder; + take?: number; + }): Promise { + let orderBy: Prisma.Enumerable = + [{ symbol: 'asc' }]; const where: Prisma.SymbolProfileWhereInput = {}; const { ASSET_SUB_CLASS: filtersByAssetSubClass } = groupBy( @@ -109,42 +125,33 @@ export class AdminService { } ); - const marketData = await this.prismaService.marketData.groupBy({ + const marketDataItems = await this.prismaService.marketData.groupBy({ _count: true, by: ['dataSource', 'symbol'] }); - let currencyPairsToGather: AdminMarketDataItem[] = []; - if (filtersByAssetSubClass) { where.assetSubClass = AssetSubClass[filtersByAssetSubClass[0].id]; - } else { - currencyPairsToGather = this.exchangeRateDataService - .getCurrencyPairs() - .map(({ dataSource, symbol }) => { - const marketDataItemCount = - marketData.find((marketDataItem) => { - return ( - marketDataItem.dataSource === dataSource && - marketDataItem.symbol === symbol - ); - })?._count ?? 0; + } - return { - dataSource, - marketDataItemCount, - symbol, - assetClass: 'CASH', - countriesCount: 0, - sectorsCount: 0 - }; - }); + if (sortColumn) { + orderBy = [{ [sortColumn]: sortDirection }]; + + if (sortColumn === 'activitiesCount') { + orderBy = { + Order: { + _count: sortDirection + } + }; + } } - const symbolProfilesToGather: AdminMarketDataItem[] = ( - await this.prismaService.symbolProfile.findMany({ + const [assetProfiles, count] = await Promise.all([ + this.prismaService.symbolProfile.findMany({ + orderBy, + skip, + take, where, - orderBy: [{ symbol: 'asc' }], select: { _count: { select: { Order: true } @@ -163,38 +170,48 @@ export class AdminService { sectors: true, symbol: true } - }) - ).map((symbolProfile) => { - const countriesCount = symbolProfile.countries - ? Object.keys(symbolProfile.countries).length - : 0; - const marketDataItemCount = - marketData.find((marketDataItem) => { - return ( - marketDataItem.dataSource === symbolProfile.dataSource && - marketDataItem.symbol === symbolProfile.symbol - ); - })?._count ?? 0; - const sectorsCount = symbolProfile.sectors - ? Object.keys(symbolProfile.sectors).length - : 0; - - return { - countriesCount, - marketDataItemCount, - sectorsCount, - activitiesCount: symbolProfile._count.Order, - assetClass: symbolProfile.assetClass, - assetSubClass: symbolProfile.assetSubClass, - comment: symbolProfile.comment, - dataSource: symbolProfile.dataSource, - date: symbolProfile.Order?.[0]?.date, - symbol: symbolProfile.symbol - }; - }); + }), + this.prismaService.symbolProfile.count({ where }) + ]); return { - marketData: [...currencyPairsToGather, ...symbolProfilesToGather] + count, + marketData: assetProfiles.map( + ({ + _count, + assetClass, + assetSubClass, + comment, + countries, + dataSource, + Order, + sectors, + symbol + }) => { + const countriesCount = countries ? Object.keys(countries).length : 0; + const marketDataItemCount = + marketDataItems.find((marketDataItem) => { + return ( + marketDataItem.dataSource === dataSource && + marketDataItem.symbol === symbol + ); + })?._count ?? 0; + const sectorsCount = sectors ? Object.keys(sectors).length : 0; + + return { + assetClass, + assetSubClass, + comment, + countriesCount, + dataSource, + symbol, + marketDataItemCount, + sectorsCount, + activitiesCount: _count.Order, + date: Order?.[0]?.date + }; + } + ) }; } 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 6ce2acd1f..c14a38882 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 @@ -1,4 +1,5 @@ import { + AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, @@ -7,17 +8,16 @@ import { ViewChild } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; -import { MatSort } from '@angular/material/sort'; +import { MatSort, Sort } 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 { Filter, UniqueAsset, User } from '@ghostfolio/common/interfaces'; import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface'; import { translate } from '@ghostfolio/ui/i18n'; -import { AssetSubClass, DataSource } from '@prisma/client'; +import { AssetSubClass, DataSource, Prisma } from '@prisma/client'; import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject } from 'rxjs'; import { distinctUntilChanged, switchMap, takeUntil } from 'rxjs/operators'; @@ -26,6 +26,8 @@ import { AssetProfileDialog } from './asset-profile-dialog/asset-profile-dialog. import { AssetProfileDialogParams } from './asset-profile-dialog/interfaces/interfaces'; import { CreateAssetProfileDialog } from './create-asset-profile-dialog/create-asset-profile-dialog.component'; import { CreateAssetProfileDialogParams } from './create-asset-profile-dialog/interfaces/interfaces'; +import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; +import { MatPaginator, PageEvent } from '@angular/material/paginator'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -33,7 +35,10 @@ import { CreateAssetProfileDialogParams } from './create-asset-profile-dialog/in styleUrls: ['./admin-market-data.scss'], templateUrl: './admin-market-data.html' }) -export class AdminMarketDataComponent implements OnDestroy, OnInit { +export class AdminMarketDataComponent + implements AfterViewInit, OnDestroy, OnInit +{ + @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; public activeFilters: Filter[] = []; @@ -75,6 +80,8 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit { public filters$ = new Subject(); public isLoading = false; public placeholder = ''; + public pageSize = DEFAULT_PAGE_SIZE; + public totalItems = 0; public user: User; private unsubscribeSubject = new Subject(); @@ -82,7 +89,6 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit { public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService, private deviceService: DeviceDetectorService, private dialog: MatDialog, private route: ActivatedRoute, @@ -117,34 +123,40 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit { ); } }); + + this.filters$ + .pipe(distinctUntilChanged(), takeUntil(this.unsubscribeSubject)) + .subscribe((filters) => { + this.activeFilters = filters; + + this.loadData(); + }); } - public ngOnInit() { - this.deviceType = this.deviceService.getDeviceInfo().deviceType; + public ngAfterViewInit() { + this.sort.sortChange.subscribe( + ({ active: sortColumn, direction }: Sort) => { + this.paginator.pageIndex = 0; - 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.loadData({ + sortColumn, + sortDirection: direction, + pageIndex: this.paginator.pageIndex + }); + } + ); + } - this.isLoading = false; + public ngOnInit() { + this.deviceType = this.deviceService.getDeviceInfo().deviceType; + } - this.changeDetectorRef.markForCheck(); - }); + public onChangePage(page: PageEvent) { + this.loadData({ + pageIndex: page.pageIndex, + sortColumn: this.sort.active, + sortDirection: this.sort.direction + }); } public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) { @@ -212,6 +224,47 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit { this.unsubscribeSubject.complete(); } + private loadData( + { + pageIndex, + sortColumn, + sortDirection + }: { + pageIndex: number; + sortColumn?: string; + sortDirection?: Prisma.SortOrder; + } = { pageIndex: 0 } + ) { + this.isLoading = true; + + if (pageIndex === 0 && this.paginator) { + this.paginator.pageIndex = 0; + } + + this.placeholder = + this.activeFilters.length <= 0 ? $localize`Filter by...` : ''; + + this.adminService + .fetchAdminMarketData({ + sortColumn, + sortDirection, + filters: this.activeFilters, + skip: pageIndex * this.pageSize, + take: this.pageSize + }) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ count, marketData }) => { + this.totalItems = count; + + this.dataSource = new MatTableDataSource(marketData); + this.dataSource.sort = this.sort; + + this.isLoading = false; + + this.changeDetectorRef.markForCheck(); + }); + } + private openAssetProfileDialog({ dataSource, symbol @@ -274,8 +327,9 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit { this.isLoading = true; this.changeDetectorRef.markForCheck(); - return this.dataService.fetchAdminMarketData({ - filters: this.activeFilters + return this.adminService.fetchAdminMarketData({ + filters: this.activeFilters, + take: this.pageSize }); }), takeUntil(this.unsubscribeSubject) 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 728d32e8c..80ba30a2a 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 @@ -56,7 +56,7 @@ - + First Activity @@ -74,7 +74,7 @@ - + Historical Data @@ -83,7 +83,7 @@ - + Sectors Count @@ -92,7 +92,7 @@ - + Countries Count @@ -162,6 +162,28 @@ (click)="onOpenAssetProfileDialog({ dataSource: row.dataSource, symbol: row.symbol })" > + + + + 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 ffb3d4ee2..060e8a6b0 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,10 +2,12 @@ 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 { MatPaginatorModule } from '@angular/material/paginator'; import { MatSortModule } from '@angular/material/sort'; import { MatTableModule } from '@angular/material/table'; import { RouterModule } from '@angular/router'; import { GfActivitiesFilterModule } from '@ghostfolio/ui/activities-filter/activities-filter.module'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { AdminMarketDataComponent } from './admin-market-data.component'; import { GfAssetProfileDialogModule } from './asset-profile-dialog/asset-profile-dialog.module'; @@ -20,8 +22,10 @@ import { GfCreateAssetProfileDialogModule } from './create-asset-profile-dialog/ GfCreateAssetProfileDialogModule, MatButtonModule, MatMenuModule, + MatPaginatorModule, MatSortModule, MatTableModule, + NgxSkeletonLoaderModule, RouterModule ], schemas: [CUSTOM_ELEMENTS_SCHEMA] diff --git a/apps/client/src/app/components/admin-overview/admin-overview.component.ts b/apps/client/src/app/components/admin-overview/admin-overview.component.ts index 5aaa3e518..2053c4298 100644 --- a/apps/client/src/app/components/admin-overview/admin-overview.component.ts +++ b/apps/client/src/app/components/admin-overview/admin-overview.component.ts @@ -1,5 +1,6 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { MatCheckboxChange } from '@angular/material/checkbox'; +import { AdminService } from '@ghostfolio/client/services/admin.service'; import { CacheService } from '@ghostfolio/client/services/cache.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; @@ -45,6 +46,7 @@ export class AdminOverviewComponent implements OnDestroy, OnInit { private unsubscribeSubject = new Subject(); public constructor( + private adminService: AdminService, private cacheService: CacheService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, @@ -197,7 +199,7 @@ export class AdminOverviewComponent implements OnDestroy, OnInit { } private fetchAdminData() { - this.dataService + this.adminService .fetchAdminData() .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ exchangeRates, settings, transactionCount, userCount }) => { diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index 3b5ae9758..48783c91b 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -1,4 +1,5 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; @@ -30,6 +31,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit { private unsubscribeSubject = new Subject(); public constructor( + private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, private impersonationStorageService: ImpersonationStorageService, @@ -112,7 +114,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit { } private fetchAdminData() { - this.dataService + this.adminService .fetchAdminData() .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ users }) => { diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts index 8b126898f..856fe9341 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/apps/client/src/app/services/admin.service.ts @@ -7,21 +7,28 @@ import { UpdatePlatformDto } from '@ghostfolio/api/app/platform/update-platform. import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { + AdminData, AdminJobs, + AdminMarketData, AdminMarketDataDetails, EnhancedSymbolProfile, + Filter, UniqueAsset } from '@ghostfolio/common/interfaces'; -import { DataSource, MarketData, Platform } from '@prisma/client'; +import { DataSource, MarketData, Platform, Prisma } from '@prisma/client'; import { JobStatus } from 'bull'; import { format, parseISO } from 'date-fns'; import { Observable, map } from 'rxjs'; +import { DataService } from './data.service'; @Injectable({ providedIn: 'root' }) export class AdminService { - public constructor(private http: HttpClient) {} + public constructor( + private dataService: DataService, + private http: HttpClient + ) {} public addAssetProfile({ dataSource, symbol }: UniqueAsset) { return this.http.post( @@ -56,6 +63,44 @@ export class AdminService { ); } + public fetchAdminData() { + return this.http.get('/api/v1/admin'); + } + + public fetchAdminMarketData({ + filters, + skip, + sortColumn, + sortDirection, + take + }: { + filters?: Filter[]; + skip?: number; + sortColumn?: string; + sortDirection?: Prisma.SortOrder; + take: number; + }) { + let params = this.dataService.buildFiltersAsQueryParams({ filters }); + + if (skip) { + params = params.append('skip', skip); + } + + if (sortColumn) { + params = params.append('sortColumn', sortColumn); + } + + if (sortDirection) { + params = params.append('sortDirection', sortDirection); + } + + params = params.append('take', take); + + return this.http.get('/api/v1/admin/market-data', { + params + }); + } + public fetchAdminMarketDataBySymbol({ dataSource, symbol diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 4aa590493..f2e5169db 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -18,8 +18,6 @@ import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { Access, Accounts, - AdminData, - AdminMarketData, BenchmarkMarketDataDetails, BenchmarkResponse, Export, @@ -51,6 +49,67 @@ import { map } from 'rxjs/operators'; export class DataService { public constructor(private http: HttpClient) {} + public buildFiltersAsQueryParams({ filters }: { filters?: Filter[] }) { + let params = new HttpParams(); + + if (filters?.length > 0) { + const { + ACCOUNT: filtersByAccount, + ASSET_CLASS: filtersByAssetClass, + ASSET_SUB_CLASS: filtersByAssetSubClass, + TAG: filtersByTag + } = groupBy(filters, (filter) => { + return filter.type; + }); + + if (filtersByAccount) { + params = params.append( + 'accounts', + filtersByAccount + .map(({ id }) => { + return id; + }) + .join(',') + ); + } + + if (filtersByAssetClass) { + params = params.append( + 'assetClasses', + filtersByAssetClass + .map(({ id }) => { + return id; + }) + .join(',') + ); + } + + if (filtersByAssetSubClass) { + params = params.append( + 'assetSubClasses', + filtersByAssetSubClass + .map(({ id }) => { + return id; + }) + .join(',') + ); + } + + if (filtersByTag) { + params = params.append( + 'tags', + filtersByTag + .map(({ id }) => { + return id; + }) + .join(',') + ); + } + } + + return params; + } + public createCheckoutSession({ couponId, priceId @@ -92,16 +151,6 @@ export class DataService { ); } - public fetchAdminData() { - return this.http.get('/api/v1/admin'); - } - - public fetchAdminMarketData({ filters }: { filters?: Filter[] }) { - return this.http.get('/api/v1/admin/market-data', { - params: this.buildFiltersAsQueryParams({ filters }) - }); - } - public fetchDividends({ filters, groupBy = 'month', @@ -450,65 +499,4 @@ export class DataService { couponCode }); } - - private buildFiltersAsQueryParams({ filters }: { filters?: Filter[] }) { - let params = new HttpParams(); - - if (filters?.length > 0) { - const { - ACCOUNT: filtersByAccount, - ASSET_CLASS: filtersByAssetClass, - ASSET_SUB_CLASS: filtersByAssetSubClass, - TAG: filtersByTag - } = groupBy(filters, (filter) => { - return filter.type; - }); - - if (filtersByAccount) { - params = params.append( - 'accounts', - filtersByAccount - .map(({ id }) => { - return id; - }) - .join(',') - ); - } - - if (filtersByAssetClass) { - params = params.append( - 'assetClasses', - filtersByAssetClass - .map(({ id }) => { - return id; - }) - .join(',') - ); - } - - if (filtersByAssetSubClass) { - params = params.append( - 'assetSubClasses', - filtersByAssetSubClass - .map(({ id }) => { - return id; - }) - .join(',') - ); - } - - if (filtersByTag) { - params = params.append( - 'tags', - filtersByTag - .map(({ id }) => { - return id; - }) - .join(',') - ); - } - } - - return params; - } } diff --git a/libs/common/src/lib/interfaces/admin-market-data.interface.ts b/libs/common/src/lib/interfaces/admin-market-data.interface.ts index 437b2975d..d53562a23 100644 --- a/libs/common/src/lib/interfaces/admin-market-data.interface.ts +++ b/libs/common/src/lib/interfaces/admin-market-data.interface.ts @@ -1,6 +1,7 @@ import { AssetClass, AssetSubClass, DataSource } from '@prisma/client'; export interface AdminMarketData { + count: number; marketData: AdminMarketDataItem[]; } diff --git a/libs/ui/src/lib/activities-table/activities-table.component.html b/libs/ui/src/lib/activities-table/activities-table.component.html index 5fa80124e..3ce9aeb7b 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -562,14 +562,13 @@