From f533f3e86203d2a7b6ba6decacc19bb5c0c08700 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 6 Dec 2023 21:00:51 +0100 Subject: [PATCH] Add pagination logic --- apps/api/src/app/order/order.controller.ts | 6 +- apps/api/src/app/order/order.service.ts | 17 +++++- .../activities/activities-page.component.ts | 56 ++++++++++++++----- apps/client/src/app/services/data.service.ts | 50 ++++++++++++----- .../activities-table-lazy.component.html | 15 +---- .../activities-table-lazy.component.scss | 12 ++-- .../activities-table-lazy.component.ts | 8 +-- .../activities-table-lazy.module.ts | 1 - 8 files changed, 104 insertions(+), 61 deletions(-) diff --git a/apps/api/src/app/order/order.controller.ts b/apps/api/src/app/order/order.controller.ts index 8c8e3e27a..5c530e467 100644 --- a/apps/api/src/app/order/order.controller.ts +++ b/apps/api/src/app/order/order.controller.ts @@ -24,7 +24,7 @@ import { } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; -import { Order as OrderModel } from '@prisma/client'; +import { Order as OrderModel, Prisma } from '@prisma/client'; import { parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; @@ -89,6 +89,8 @@ export class OrderController { @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId, @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, + @Query('sortColumn') sortColumn?: string, + @Query('sortDirection') sortDirection?: Prisma.SortOrder, @Query('skip') skip?: number, @Query('tags') filterByTags?: string, @Query('take') take?: number @@ -105,6 +107,8 @@ export class OrderController { const activities = await this.orderService.getOrders({ filters, + sortColumn, + sortDirection, userCurrency, includeDrafts: true, skip: isNaN(skip) ? undefined : skip, diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 10515018c..6e2b11001 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -51,7 +51,7 @@ export class OrderService { take?: number; cursor?: Prisma.OrderWhereUniqueInput; where?: Prisma.OrderWhereInput; - orderBy?: Prisma.OrderOrderByWithRelationInput; + orderBy?: Prisma.Enumerable; }): Promise { const { include, skip, take, cursor, where, orderBy } = params; @@ -231,6 +231,8 @@ export class OrderService { filters, includeDrafts = false, skip, + sortColumn, + sortDirection, take = Number.MAX_SAFE_INTEGER, types, userCurrency, @@ -240,12 +242,17 @@ export class OrderService { filters?: Filter[]; includeDrafts?: boolean; skip?: number; + sortColumn?: string; + sortDirection?: Prisma.SortOrder; take?: number; types?: TypeOfOrder[]; userCurrency: string; userId: string; withExcludedAccounts?: boolean; }): Promise { + let orderBy: Prisma.Enumerable = [ + { date: 'asc' } + ]; const where: Prisma.OrderWhereInput = { userId }; const { @@ -307,6 +314,10 @@ export class OrderService { }; } + if (sortColumn) { + orderBy = [{ [sortColumn]: sortDirection }]; + } + if (types) { where.OR = types.map((type) => { return { @@ -319,6 +330,7 @@ export class OrderService { return ( await this.orders({ + orderBy, skip, take, where, @@ -332,8 +344,7 @@ export class OrderService { // eslint-disable-next-line @typescript-eslint/naming-convention SymbolProfile: true, tags: true - }, - orderBy: { date: 'asc' } + } }) ) .filter((order) => { diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts index 67b56ea60..8a790044a 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts @@ -16,7 +16,7 @@ import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { downloadAsFile } from '@ghostfolio/common/helper'; import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; -import { DataSource, Order as OrderModel } from '@prisma/client'; +import { DataSource, Order as OrderModel, Prisma } from '@prisma/client'; import { format, parseISO } from 'date-fns'; import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject, Subscription } from 'rxjs'; @@ -42,6 +42,8 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit { public pageIndex = 0; public pageSize = DEFAULT_PAGE_SIZE; public routeQueryParams: Subscription; + public sortColumn = 'date'; + public sortDirection: Prisma.SortOrder = 'desc'; public user: User; private unsubscribeSubject = new Subject(); @@ -109,26 +111,50 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit { } public fetchActivities() { - this.dataService - .fetchActivities({}) - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ activities }) => { - this.activities = activities; - this.dataSource = new MatTableDataSource(activities); + if (this.user?.settings?.isExperimentalFeatures === true) { + this.dataService + .fetchActivities({ + sortColumn: this.sortColumn, + sortDirection: this.sortDirection, + skip: this.pageIndex * this.pageSize, + take: this.pageSize + }) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ activities }) => { + this.dataSource = new MatTableDataSource(activities); + + if ( + this.hasPermissionToCreateActivity && + this.activities?.length <= 0 + ) { + this.router.navigate([], { queryParams: { createDialog: true } }); + } - if ( - this.hasPermissionToCreateActivity && - this.activities?.length <= 0 - ) { - this.router.navigate([], { queryParams: { createDialog: true } }); - } + this.changeDetectorRef.markForCheck(); + }); + } else { + this.dataService + .fetchActivities({}) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ activities }) => { + this.activities = activities; + + if ( + this.hasPermissionToCreateActivity && + this.activities?.length <= 0 + ) { + this.router.navigate([], { queryParams: { createDialog: true } }); + } - this.changeDetectorRef.markForCheck(); - }); + this.changeDetectorRef.markForCheck(); + }); + } } public onChangePage(page: PageEvent) { this.pageIndex = page.pageIndex; + + this.fetchActivities(); } public onCloneActivity(aActivity: Activity) { diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index e61fa2406..16d68639c 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -38,7 +38,7 @@ import { } from '@ghostfolio/common/interfaces'; import { filterGlobalPermissions } from '@ghostfolio/common/permissions'; import { AccountWithValue, DateRange, GroupBy } from '@ghostfolio/common/types'; -import { DataSource, Order as OrderModel } from '@prisma/client'; +import { DataSource, Order as OrderModel, Prisma } from '@prisma/client'; import { format, parseISO } from 'date-fns'; import { cloneDeep, groupBy, isNumber } from 'lodash'; import { Observable } from 'rxjs'; @@ -149,23 +149,45 @@ export class DataService { } public fetchActivities({ - filters + filters, + skip, + sortColumn, + sortDirection, + take }: { filters?: Filter[]; + skip?: number; + sortColumn?: string; + sortDirection?: Prisma.SortOrder; + take?: number; }): Observable { - return this.http - .get('/api/v1/order', { - params: this.buildFiltersAsQueryParams({ filters }) + let params = this.buildFiltersAsQueryParams({ filters }); + + if (skip) { + params = params.append('skip', skip); + } + + if (sortColumn) { + params = params.append('sortColumn', sortColumn); + } + + if (sortDirection) { + params = params.append('sortDirection', sortDirection); + } + + if (take) { + params = params.append('take', take); + } + + return this.http.get('/api/v1/order', { params }).pipe( + map(({ activities }) => { + for (const activity of activities) { + activity.createdAt = parseISO(activity.createdAt); + activity.date = parseISO(activity.date); + } + return { activities }; }) - .pipe( - map(({ activities }) => { - for (const activity of activities) { - activity.createdAt = parseISO(activity.createdAt); - activity.date = parseISO(activity.date); - } - return { activities }; - }) - ); + ); } public fetchDividends({ diff --git a/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.html b/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.html index 8fbe9ed22..ca3f10f7e 100644 --- a/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.html +++ b/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.html @@ -65,14 +65,7 @@
- +
diff --git a/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.scss b/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.scss index ea3dad292..867e80e18 100644 --- a/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.scss +++ b/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.scss @@ -5,15 +5,11 @@ .activities { overflow-x: auto; + } - .mat-mdc-table { - th { - ::ng-deep { - .mat-sort-header-container { - justify-content: inherit; - } - } - } + ::ng-deep { + .mat-mdc-paginator-range-label { + display: none; } } } diff --git a/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.ts b/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.ts index 66c310f3b..93a60041e 100644 --- a/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.ts +++ b/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.ts @@ -11,7 +11,6 @@ import { ViewChild } from '@angular/core'; import { MatPaginator, PageEvent } from '@angular/material/paginator'; -import { MatSort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; import { Router } from '@angular/router'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; @@ -21,7 +20,6 @@ import { UniqueAsset } from '@ghostfolio/common/interfaces'; import { OrderWithAccount } from '@ghostfolio/common/types'; import { isUUID } from 'class-validator'; import { endOfToday, isAfter } from 'date-fns'; -import { get } from 'lodash'; import { Subject, Subscription, takeUntil } from 'rxjs'; @Component({ @@ -39,6 +37,7 @@ export class ActivitiesTableLazyComponent @Input() hasPermissionToCreateActivity: boolean; @Input() hasPermissionToExportActivities: boolean; @Input() hasPermissionToOpenDetails = true; + @Input() length = Number.MAX_SAFE_INTEGER; @Input() locale: string; @Input() pageIndex: number; @Input() pageSize = DEFAULT_PAGE_SIZE; @@ -59,7 +58,6 @@ export class ActivitiesTableLazyComponent @Output() selectedActivities = new EventEmitter(); @ViewChild(MatPaginator) paginator: MatPaginator; - @ViewChild(MatSort) sort: MatSort; public defaultDateFormat: string; public displayedColumns = []; @@ -128,10 +126,6 @@ export class ActivitiesTableLazyComponent } if (this.dataSource) { - this.dataSource.paginator = this.paginator; - this.dataSource.sort = this.sort; - this.dataSource.sortingDataAccessor = get; - this.isLoading = false; } } diff --git a/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.module.ts b/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.module.ts index 22c9f6354..3d7e79a4b 100644 --- a/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.module.ts +++ b/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.module.ts @@ -31,7 +31,6 @@ import { ActivitiesTableLazyComponent } from './activities-table-lazy.component' MatCheckboxModule, MatMenuModule, MatPaginatorModule, - MatSortModule, MatTableModule, MatTooltipModule, NgxSkeletonLoaderModule,