Browse Source

Add pagination logic

pull/2729/head
Thomas Kaul 2 years ago
parent
commit
f533f3e862
  1. 6
      apps/api/src/app/order/order.controller.ts
  2. 17
      apps/api/src/app/order/order.service.ts
  3. 30
      apps/client/src/app/pages/portfolio/activities/activities-page.component.ts
  4. 36
      apps/client/src/app/services/data.service.ts
  5. 15
      libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.html
  6. 10
      libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.scss
  7. 8
      libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.ts
  8. 1
      libs/ui/src/lib/activities-table-lazy/activities-table-lazy.module.ts

6
apps/api/src/app/order/order.controller.ts

@ -24,7 +24,7 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { REQUEST } from '@nestjs/core'; import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport'; 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 { parseISO } from 'date-fns';
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { StatusCodes, getReasonPhrase } from 'http-status-codes';
@ -89,6 +89,8 @@ export class OrderController {
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId, @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId,
@Query('accounts') filterByAccounts?: string, @Query('accounts') filterByAccounts?: string,
@Query('assetClasses') filterByAssetClasses?: string, @Query('assetClasses') filterByAssetClasses?: string,
@Query('sortColumn') sortColumn?: string,
@Query('sortDirection') sortDirection?: Prisma.SortOrder,
@Query('skip') skip?: number, @Query('skip') skip?: number,
@Query('tags') filterByTags?: string, @Query('tags') filterByTags?: string,
@Query('take') take?: number @Query('take') take?: number
@ -105,6 +107,8 @@ export class OrderController {
const activities = await this.orderService.getOrders({ const activities = await this.orderService.getOrders({
filters, filters,
sortColumn,
sortDirection,
userCurrency, userCurrency,
includeDrafts: true, includeDrafts: true,
skip: isNaN(skip) ? undefined : skip, skip: isNaN(skip) ? undefined : skip,

17
apps/api/src/app/order/order.service.ts

@ -51,7 +51,7 @@ export class OrderService {
take?: number; take?: number;
cursor?: Prisma.OrderWhereUniqueInput; cursor?: Prisma.OrderWhereUniqueInput;
where?: Prisma.OrderWhereInput; where?: Prisma.OrderWhereInput;
orderBy?: Prisma.OrderOrderByWithRelationInput; orderBy?: Prisma.Enumerable<Prisma.OrderOrderByWithRelationInput>;
}): Promise<OrderWithAccount[]> { }): Promise<OrderWithAccount[]> {
const { include, skip, take, cursor, where, orderBy } = params; const { include, skip, take, cursor, where, orderBy } = params;
@ -231,6 +231,8 @@ export class OrderService {
filters, filters,
includeDrafts = false, includeDrafts = false,
skip, skip,
sortColumn,
sortDirection,
take = Number.MAX_SAFE_INTEGER, take = Number.MAX_SAFE_INTEGER,
types, types,
userCurrency, userCurrency,
@ -240,12 +242,17 @@ export class OrderService {
filters?: Filter[]; filters?: Filter[];
includeDrafts?: boolean; includeDrafts?: boolean;
skip?: number; skip?: number;
sortColumn?: string;
sortDirection?: Prisma.SortOrder;
take?: number; take?: number;
types?: TypeOfOrder[]; types?: TypeOfOrder[];
userCurrency: string; userCurrency: string;
userId: string; userId: string;
withExcludedAccounts?: boolean; withExcludedAccounts?: boolean;
}): Promise<Activity[]> { }): Promise<Activity[]> {
let orderBy: Prisma.Enumerable<Prisma.OrderOrderByWithRelationInput> = [
{ date: 'asc' }
];
const where: Prisma.OrderWhereInput = { userId }; const where: Prisma.OrderWhereInput = { userId };
const { const {
@ -307,6 +314,10 @@ export class OrderService {
}; };
} }
if (sortColumn) {
orderBy = [{ [sortColumn]: sortDirection }];
}
if (types) { if (types) {
where.OR = types.map((type) => { where.OR = types.map((type) => {
return { return {
@ -319,6 +330,7 @@ export class OrderService {
return ( return (
await this.orders({ await this.orders({
orderBy,
skip, skip,
take, take,
where, where,
@ -332,8 +344,7 @@ export class OrderService {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
SymbolProfile: true, SymbolProfile: true,
tags: true tags: true
}, }
orderBy: { date: 'asc' }
}) })
) )
.filter((order) => { .filter((order) => {

30
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 { downloadAsFile } from '@ghostfolio/common/helper';
import { User } from '@ghostfolio/common/interfaces'; import { User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; 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 { format, parseISO } from 'date-fns';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, Subscription } from 'rxjs'; import { Subject, Subscription } from 'rxjs';
@ -42,6 +42,8 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
public pageIndex = 0; public pageIndex = 0;
public pageSize = DEFAULT_PAGE_SIZE; public pageSize = DEFAULT_PAGE_SIZE;
public routeQueryParams: Subscription; public routeQueryParams: Subscription;
public sortColumn = 'date';
public sortDirection: Prisma.SortOrder = 'desc';
public user: User; public user: User;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -109,12 +111,33 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
} }
public fetchActivities() { public fetchActivities() {
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 } });
}
this.changeDetectorRef.markForCheck();
});
} else {
this.dataService this.dataService
.fetchActivities({}) .fetchActivities({})
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ activities }) => { .subscribe(({ activities }) => {
this.activities = activities; this.activities = activities;
this.dataSource = new MatTableDataSource(activities);
if ( if (
this.hasPermissionToCreateActivity && this.hasPermissionToCreateActivity &&
@ -126,9 +149,12 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
} }
}
public onChangePage(page: PageEvent) { public onChangePage(page: PageEvent) {
this.pageIndex = page.pageIndex; this.pageIndex = page.pageIndex;
this.fetchActivities();
} }
public onCloneActivity(aActivity: Activity) { public onCloneActivity(aActivity: Activity) {

36
apps/client/src/app/services/data.service.ts

@ -38,7 +38,7 @@ import {
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { filterGlobalPermissions } from '@ghostfolio/common/permissions'; import { filterGlobalPermissions } from '@ghostfolio/common/permissions';
import { AccountWithValue, DateRange, GroupBy } from '@ghostfolio/common/types'; 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 { format, parseISO } from 'date-fns';
import { cloneDeep, groupBy, isNumber } from 'lodash'; import { cloneDeep, groupBy, isNumber } from 'lodash';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@ -149,15 +149,37 @@ export class DataService {
} }
public fetchActivities({ public fetchActivities({
filters filters,
skip,
sortColumn,
sortDirection,
take
}: { }: {
filters?: Filter[]; filters?: Filter[];
skip?: number;
sortColumn?: string;
sortDirection?: Prisma.SortOrder;
take?: number;
}): Observable<Activities> { }): Observable<Activities> {
return this.http let params = this.buildFiltersAsQueryParams({ filters });
.get<any>('/api/v1/order', {
params: this.buildFiltersAsQueryParams({ filters }) if (skip) {
}) params = params.append('skip', skip);
.pipe( }
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<any>('/api/v1/order', { params }).pipe(
map(({ activities }) => { map(({ activities }) => {
for (const activity of activities) { for (const activity of activities) {
activity.createdAt = parseISO(activity.createdAt); activity.createdAt = parseISO(activity.createdAt);

15
libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.html

@ -65,14 +65,7 @@
</div> </div>
<div class="activities"> <div class="activities">
<table <table class="gf-table w-100" mat-table [dataSource]="dataSource">
class="gf-table w-100"
mat-table
matSort
matSortActive="date"
matSortDirection="desc"
[dataSource]="dataSource"
>
<ng-container matColumnDef="select"> <ng-container matColumnDef="select">
<th *matHeaderCellDef class="px-1" mat-header-cell> <th *matHeaderCellDef class="px-1" mat-header-cell>
<mat-checkbox <mat-checkbox
@ -476,13 +469,11 @@
</div> </div>
<mat-paginator <mat-paginator
[length]="length"
[ngClass]="{ [ngClass]="{
'd-none': 'd-none': isLoading && dataSource?.data.length === 0
(isLoading && dataSource?.data.length === 0) ||
dataSource?.data.length <= pageSize
}" }"
[pageSize]="pageSize" [pageSize]="pageSize"
[showFirstLastButtons]="true"
(page)="onChangePage($event)" (page)="onChangePage($event)"
></mat-paginator> ></mat-paginator>

10
libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.scss

@ -5,15 +5,11 @@
.activities { .activities {
overflow-x: auto; overflow-x: auto;
}
.mat-mdc-table {
th {
::ng-deep { ::ng-deep {
.mat-sort-header-container { .mat-mdc-paginator-range-label {
justify-content: inherit; display: none;
}
}
}
} }
} }
} }

8
libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.ts

@ -11,7 +11,6 @@ import {
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { MatPaginator, PageEvent } from '@angular/material/paginator'; import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; 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 { OrderWithAccount } from '@ghostfolio/common/types';
import { isUUID } from 'class-validator'; import { isUUID } from 'class-validator';
import { endOfToday, isAfter } from 'date-fns'; import { endOfToday, isAfter } from 'date-fns';
import { get } from 'lodash';
import { Subject, Subscription, takeUntil } from 'rxjs'; import { Subject, Subscription, takeUntil } from 'rxjs';
@Component({ @Component({
@ -39,6 +37,7 @@ export class ActivitiesTableLazyComponent
@Input() hasPermissionToCreateActivity: boolean; @Input() hasPermissionToCreateActivity: boolean;
@Input() hasPermissionToExportActivities: boolean; @Input() hasPermissionToExportActivities: boolean;
@Input() hasPermissionToOpenDetails = true; @Input() hasPermissionToOpenDetails = true;
@Input() length = Number.MAX_SAFE_INTEGER;
@Input() locale: string; @Input() locale: string;
@Input() pageIndex: number; @Input() pageIndex: number;
@Input() pageSize = DEFAULT_PAGE_SIZE; @Input() pageSize = DEFAULT_PAGE_SIZE;
@ -59,7 +58,6 @@ export class ActivitiesTableLazyComponent
@Output() selectedActivities = new EventEmitter<Activity[]>(); @Output() selectedActivities = new EventEmitter<Activity[]>();
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
public defaultDateFormat: string; public defaultDateFormat: string;
public displayedColumns = []; public displayedColumns = [];
@ -128,10 +126,6 @@ export class ActivitiesTableLazyComponent
} }
if (this.dataSource) { if (this.dataSource) {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
this.dataSource.sortingDataAccessor = get;
this.isLoading = false; this.isLoading = false;
} }
} }

1
libs/ui/src/lib/activities-table-lazy/activities-table-lazy.module.ts

@ -31,7 +31,6 @@ import { ActivitiesTableLazyComponent } from './activities-table-lazy.component'
MatCheckboxModule, MatCheckboxModule,
MatMenuModule, MatMenuModule,
MatPaginatorModule, MatPaginatorModule,
MatSortModule,
MatTableModule, MatTableModule,
MatTooltipModule, MatTooltipModule,
NgxSkeletonLoaderModule, NgxSkeletonLoaderModule,

Loading…
Cancel
Save