Browse Source

Feature/add support for column sorting to lazy loaded activities table (#2738)

* Add support for column sorting

* Update changelog
pull/2743/head
Thomas Kaul 1 year ago
committed by GitHub
parent
commit
b22edff16b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 10
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  3. 13
      apps/client/src/app/pages/portfolio/activities/activities-page.component.ts
  4. 3
      apps/client/src/app/pages/portfolio/activities/activities-page.html
  5. 5
      apps/client/src/app/services/admin.service.ts
  6. 5
      apps/client/src/app/services/data.service.ts
  7. 34
      libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.html
  8. 14
      libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.ts
  9. 1
      libs/ui/src/lib/activities-table-lazy/activities-table-lazy.module.ts

1
CHANGELOG.md

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Added support for column sorting to the lazy-loaded activities table on the portfolio activities page (experimental)
- Extended the benchmarks of the markets overview by the current market condition (all time high) - Extended the benchmarks of the markets overview by the current market condition (all time high)
### Changed ### Changed

10
apps/client/src/app/components/admin-market-data/admin-market-data.component.ts

@ -9,7 +9,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatPaginator, PageEvent } from '@angular/material/paginator'; import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { MatSort, Sort } from '@angular/material/sort'; import { MatSort, Sort, SortDirection } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { AdminService } from '@ghostfolio/client/services/admin.service'; import { AdminService } from '@ghostfolio/client/services/admin.service';
@ -19,7 +19,7 @@ import { getDateFormatString } from '@ghostfolio/common/helper';
import { Filter, UniqueAsset, User } from '@ghostfolio/common/interfaces'; import { Filter, UniqueAsset, User } from '@ghostfolio/common/interfaces';
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface'; import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
import { AssetSubClass, DataSource, Prisma } from '@prisma/client'; import { AssetSubClass, DataSource } from '@prisma/client';
import { isUUID } from 'class-validator'; import { isUUID } from 'class-validator';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@ -160,7 +160,7 @@ export class AdminMarketDataComponent
this.loadData({ this.loadData({
sortColumn, sortColumn,
sortDirection: <Prisma.SortOrder>direction, sortDirection: direction,
pageIndex: this.paginator.pageIndex pageIndex: this.paginator.pageIndex
}); });
} }
@ -175,7 +175,7 @@ export class AdminMarketDataComponent
this.loadData({ this.loadData({
pageIndex: page.pageIndex, pageIndex: page.pageIndex,
sortColumn: this.sort.active, sortColumn: this.sort.active,
sortDirection: <Prisma.SortOrder>this.sort.direction sortDirection: this.sort.direction
}); });
} }
@ -262,7 +262,7 @@ export class AdminMarketDataComponent
}: { }: {
pageIndex: number; pageIndex: number;
sortColumn?: string; sortColumn?: string;
sortDirection?: Prisma.SortOrder; sortDirection?: SortDirection;
} = { pageIndex: 0 } } = { pageIndex: 0 }
) { ) {
this.isLoading = true; this.isLoading = true;

13
apps/client/src/app/pages/portfolio/activities/activities-page.component.ts

@ -1,6 +1,7 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { PageEvent } from '@angular/material/paginator'; import { PageEvent } from '@angular/material/paginator';
import { Sort, SortDirection } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
@ -16,7 +17,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, Prisma } from '@prisma/client'; import { DataSource, Order as OrderModel } 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';
@ -43,7 +44,7 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
public pageSize = DEFAULT_PAGE_SIZE; public pageSize = DEFAULT_PAGE_SIZE;
public routeQueryParams: Subscription; public routeQueryParams: Subscription;
public sortColumn = 'date'; public sortColumn = 'date';
public sortDirection: Prisma.SortOrder = 'desc'; public sortDirection: SortDirection = 'desc';
public totalItems: number; public totalItems: number;
public user: User; public user: User;
@ -261,6 +262,14 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
}); });
} }
public onSortChanged({ active, direction }: Sort) {
this.pageIndex = 0;
this.sortColumn = active;
this.sortDirection = direction;
this.fetchActivities();
}
public onUpdateActivity(aActivity: OrderModel) { public onUpdateActivity(aActivity: OrderModel) {
this.router.navigate([], { this.router.navigate([], {
queryParams: { activityId: aActivity.id, editDialog: true } queryParams: { activityId: aActivity.id, editDialog: true }

3
apps/client/src/app/pages/portfolio/activities/activities-page.html

@ -13,6 +13,8 @@
[pageIndex]="pageIndex" [pageIndex]="pageIndex"
[pageSize]="pageSize" [pageSize]="pageSize"
[showActions]="!hasImpersonationId && hasPermissionToDeleteActivity && !user.settings.isRestrictedView" [showActions]="!hasImpersonationId && hasPermissionToDeleteActivity && !user.settings.isRestrictedView"
[sortColumn]="sortColumn"
[sortDirection]="sortDirection"
[totalItems]="totalItems" [totalItems]="totalItems"
(activityDeleted)="onDeleteActivity($event)" (activityDeleted)="onDeleteActivity($event)"
(activityToClone)="onCloneActivity($event)" (activityToClone)="onCloneActivity($event)"
@ -23,6 +25,7 @@
(import)="onImport()" (import)="onImport()"
(importDividends)="onImportDividends()" (importDividends)="onImportDividends()"
(pageChanged)="onChangePage($event)" (pageChanged)="onChangePage($event)"
(sortChanged)="onSortChanged($event)"
></gf-activities-table-lazy> ></gf-activities-table-lazy>
<gf-activities-table <gf-activities-table
*ngIf="user?.settings?.isExperimentalFeatures !== true" *ngIf="user?.settings?.isExperimentalFeatures !== true"

5
apps/client/src/app/services/admin.service.ts

@ -1,5 +1,6 @@
import { HttpClient, HttpParams } from '@angular/common/http'; import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { SortDirection } from '@angular/material/sort';
import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto'; import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto';
import { UpdateBulkMarketDataDto } from '@ghostfolio/api/app/admin/update-bulk-market-data.dto'; import { UpdateBulkMarketDataDto } from '@ghostfolio/api/app/admin/update-bulk-market-data.dto';
import { CreatePlatformDto } from '@ghostfolio/api/app/platform/create-platform.dto'; import { CreatePlatformDto } from '@ghostfolio/api/app/platform/create-platform.dto';
@ -17,7 +18,7 @@ import {
Filter, Filter,
UniqueAsset UniqueAsset
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { DataSource, MarketData, Platform, Prisma, Tag } from '@prisma/client'; import { DataSource, MarketData, Platform, Tag } from '@prisma/client';
import { JobStatus } from 'bull'; import { JobStatus } from 'bull';
import { format, parseISO } from 'date-fns'; import { format, parseISO } from 'date-fns';
import { Observable, map } from 'rxjs'; import { Observable, map } from 'rxjs';
@ -84,7 +85,7 @@ export class AdminService {
filters?: Filter[]; filters?: Filter[];
skip?: number; skip?: number;
sortColumn?: string; sortColumn?: string;
sortDirection?: Prisma.SortOrder; sortDirection?: SortDirection;
take: number; take: number;
}) { }) {
let params = this.dataService.buildFiltersAsQueryParams({ filters }); let params = this.dataService.buildFiltersAsQueryParams({ filters });

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

@ -1,5 +1,6 @@
import { HttpClient, HttpParams } from '@angular/common/http'; import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { SortDirection } from '@angular/material/sort';
import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto'; import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto';
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
import { TransferBalanceDto } from '@ghostfolio/api/app/account/transfer-balance.dto'; import { TransferBalanceDto } from '@ghostfolio/api/app/account/transfer-balance.dto';
@ -38,7 +39,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, Prisma } from '@prisma/client'; import { DataSource, Order as OrderModel } 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';
@ -158,7 +159,7 @@ export class DataService {
filters?: Filter[]; filters?: Filter[];
skip?: number; skip?: number;
sortColumn?: string; sortColumn?: string;
sortDirection?: Prisma.SortOrder; sortDirection?: SortDirection;
take?: number; take?: number;
}): Observable<Activities> { }): Observable<Activities> {
let params = this.buildFiltersAsQueryParams({ filters }); let params = this.buildFiltersAsQueryParams({ filters });

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

@ -65,7 +65,14 @@
</div> </div>
<div class="activities"> <div class="activities">
<table class="gf-table w-100" mat-table [dataSource]="dataSource"> <table
class="gf-table w-100"
mat-table
matSort
[dataSource]="dataSource"
[matSortActive]="sortColumn"
[matSortDirection]="sortDirection"
>
<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
@ -118,12 +125,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="nameWithSymbol"> <ng-container matColumnDef="nameWithSymbol">
<th <th *matHeaderCellDef class="px-1" mat-header-cell>
*matHeaderCellDef
class="px-1"
mat-header-cell
mat-sort-header="SymbolProfile.symbol"
>
<ng-container i18n>Name</ng-container> <ng-container i18n>Name</ng-container>
</th> </th>
<td *matCellDef="let element" class="line-height-1 px-1" mat-cell> <td *matCellDef="let element" class="line-height-1 px-1" mat-cell>
@ -243,7 +245,6 @@
*matHeaderCellDef *matHeaderCellDef
class="d-none d-lg-table-cell justify-content-end px-1" class="d-none d-lg-table-cell justify-content-end px-1"
mat-header-cell mat-header-cell
mat-sort-header
> >
<ng-container i18n>Value</ng-container> <ng-container i18n>Value</ng-container>
</th> </th>
@ -263,12 +264,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="currency"> <ng-container matColumnDef="currency">
<th <th *matHeaderCellDef class="d-none d-lg-table-cell px-1" mat-header-cell>
*matHeaderCellDef
class="d-none d-lg-table-cell px-1"
mat-header-cell
mat-sort-header="SymbolProfile.currency"
>
<ng-container i18n>Currency</ng-container> <ng-container i18n>Currency</ng-container>
</th> </th>
<td <td
@ -285,7 +281,6 @@
*matHeaderCellDef *matHeaderCellDef
class="d-lg-none d-xl-none justify-content-end px-1" class="d-lg-none d-xl-none justify-content-end px-1"
mat-header-cell mat-header-cell
mat-sort-header
> >
<ng-container i18n>Value</ng-container> <ng-container i18n>Value</ng-container>
</th> </th>
@ -301,12 +296,7 @@
</ng-container> </ng-container>
<ng-container matColumnDef="account"> <ng-container matColumnDef="account">
<th <th *matHeaderCellDef class="px-1" mat-header-cell>
*matHeaderCellDef
class="px-1"
mat-header-cell
mat-sort-header="Account.name"
>
<span class="d-none d-lg-block" i18n>Account</span> <span class="d-none d-lg-block" i18n>Account</span>
</th> </th>
<td *matCellDef="let element" class="px-1" mat-cell> <td *matCellDef="let element" class="px-1" mat-cell>
@ -473,7 +463,9 @@
[ngClass]="{ [ngClass]="{
'd-none': (isLoading && totalItems === 0) || totalItems <= pageSize 'd-none': (isLoading && totalItems === 0) || totalItems <= pageSize
}" }"
[pageIndex]="pageIndex"
[pageSize]="pageSize" [pageSize]="pageSize"
[showFirstLastButtons]="true"
(page)="onChangePage($event)" (page)="onChangePage($event)"
></mat-paginator> ></mat-paginator>

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

@ -1,5 +1,6 @@
import { SelectionModel } from '@angular/cdk/collections'; import { SelectionModel } from '@angular/cdk/collections';
import { import {
AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
EventEmitter, EventEmitter,
@ -11,6 +12,7 @@ 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, Sort, SortDirection } 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';
@ -29,7 +31,7 @@ import { Subject, Subscription, takeUntil } from 'rxjs';
templateUrl: './activities-table-lazy.component.html' templateUrl: './activities-table-lazy.component.html'
}) })
export class ActivitiesTableLazyComponent export class ActivitiesTableLazyComponent
implements OnChanges, OnDestroy, OnInit implements AfterViewInit, OnChanges, OnDestroy, OnInit
{ {
@Input() baseCurrency: string; @Input() baseCurrency: string;
@Input() dataSource: MatTableDataSource<Activity>; @Input() dataSource: MatTableDataSource<Activity>;
@ -44,6 +46,8 @@ export class ActivitiesTableLazyComponent
@Input() showCheckbox = false; @Input() showCheckbox = false;
@Input() showFooter = true; @Input() showFooter = true;
@Input() showNameColumn = true; @Input() showNameColumn = true;
@Input() sortColumn: string;
@Input() sortDirection: SortDirection;
@Input() totalItems = Number.MAX_SAFE_INTEGER; @Input() totalItems = Number.MAX_SAFE_INTEGER;
@Output() activityDeleted = new EventEmitter<string>(); @Output() activityDeleted = new EventEmitter<string>();
@ -56,8 +60,10 @@ export class ActivitiesTableLazyComponent
@Output() importDividends = new EventEmitter<UniqueAsset>(); @Output() importDividends = new EventEmitter<UniqueAsset>();
@Output() pageChanged = new EventEmitter<PageEvent>(); @Output() pageChanged = new EventEmitter<PageEvent>();
@Output() selectedActivities = new EventEmitter<Activity[]>(); @Output() selectedActivities = new EventEmitter<Activity[]>();
@Output() sortChanged = new EventEmitter<Sort>();
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
public defaultDateFormat: string; public defaultDateFormat: string;
public displayedColumns = []; public displayedColumns = [];
@ -86,6 +92,12 @@ export class ActivitiesTableLazyComponent
} }
} }
public ngAfterViewInit() {
this.sort.sortChange.subscribe((value: Sort) => {
this.sortChanged.emit(value);
});
}
public areAllRowsSelected() { public areAllRowsSelected() {
const numSelectedRows = this.selectedRows.selected.length; const numSelectedRows = this.selectedRows.selected.length;
const numTotalRows = this.dataSource.data.length; const numTotalRows = this.dataSource.data.length;

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

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

Loading…
Cancel
Save