From 531964636b9bc6f4abe598acef2d2380b5890d3d Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 8 Dec 2023 19:59:06 +0100 Subject: [PATCH 1/5] Fix type (#2708) --- apps/api/src/app/access/access.controller.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/api/src/app/access/access.controller.ts b/apps/api/src/app/access/access.controller.ts index dd40a19f4..59fd8605c 100644 --- a/apps/api/src/app/access/access.controller.ts +++ b/apps/api/src/app/access/access.controller.ts @@ -17,7 +17,6 @@ import { AuthGuard } from '@nestjs/passport'; import { Access as AccessModel } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; -import { AccessModule } from './access.module'; import { AccessService } from './access.service'; import { CreateAccessDto } from './create-access.dto'; @@ -83,7 +82,7 @@ export class AccessController { @Delete(':id') @UseGuards(AuthGuard('jwt')) - public async deleteAccess(@Param('id') id: string): Promise { + public async deleteAccess(@Param('id') id: string): Promise { const access = await this.accessService.access({ id }); if ( From 51a0ede3e44eae519eaa3a41b51431b4ef127ae1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 9 Dec 2023 17:12:09 +0100 Subject: [PATCH 2/5] Feature/introduce lazy loaded activities table (#2729) * Introduce lazy-loaded activities table * Add icon column * Emit paginator event * Add pagination logic * Integrate total items * Update changelog --- CHANGELOG.md | 4 + .../order/interfaces/activities.interface.ts | 1 + apps/api/src/app/order/order.controller.ts | 10 +- apps/api/src/app/order/order.service.ts | 34 +- .../src/app/portfolio/portfolio.service.ts | 55 +- .../activities/activities-page.component.ts | 64 ++- .../portfolio/activities/activities-page.html | 23 + .../activities/activities-page.module.ts | 2 + apps/client/src/app/services/data.service.ts | 50 +- .../activities-table-lazy.component.html | 499 ++++++++++++++++++ .../activities-table-lazy.component.scss | 9 + .../activities-table-lazy.component.ts | 237 +++++++++ .../activities-table-lazy.module.ts | 41 ++ 13 files changed, 962 insertions(+), 67 deletions(-) create mode 100644 libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.html create mode 100644 libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.scss create mode 100644 libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.ts create mode 100644 libs/ui/src/lib/activities-table-lazy/activities-table-lazy.module.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ff0111ba7..439767581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Introduced a lazy-loaded activities table on the portfolio activities page (experimental) + ### Changed - Set the actions columns of various tables to stick at the end diff --git a/apps/api/src/app/order/interfaces/activities.interface.ts b/apps/api/src/app/order/interfaces/activities.interface.ts index bc2c35a50..7c612d464 100644 --- a/apps/api/src/app/order/interfaces/activities.interface.ts +++ b/apps/api/src/app/order/interfaces/activities.interface.ts @@ -2,6 +2,7 @@ import { OrderWithAccount } from '@ghostfolio/common/types'; export interface Activities { activities: Activity[]; + count: number; } export interface Activity extends OrderWithAccount { diff --git a/apps/api/src/app/order/order.controller.ts b/apps/api/src/app/order/order.controller.ts index 8c8e3e27a..7e37f22b7 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'; @@ -90,6 +90,8 @@ export class OrderController { @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, @Query('skip') skip?: number, + @Query('sortColumn') sortColumn?: string, + @Query('sortDirection') sortDirection?: Prisma.SortOrder, @Query('tags') filterByTags?: string, @Query('take') take?: number ): Promise { @@ -103,8 +105,10 @@ export class OrderController { await this.impersonationService.validateImpersonationId(impersonationId); const userCurrency = this.request.user.Settings.settings.baseCurrency; - const activities = await this.orderService.getOrders({ + const { activities, count } = await this.orderService.getOrders({ filters, + sortColumn, + sortDirection, userCurrency, includeDrafts: true, skip: isNaN(skip) ? undefined : skip, @@ -113,7 +117,7 @@ export class OrderController { withExcludedAccounts: true }); - return { activities }; + return { activities, count }; } @Post() diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 10515018c..574bfdcd2 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -25,7 +25,7 @@ import { endOfToday, isAfter } from 'date-fns'; import { groupBy } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; -import { Activity } from './interfaces/activities.interface'; +import { Activities, Activity } from './interfaces/activities.interface'; @Injectable() export class OrderService { @@ -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 { + }): 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 { @@ -317,8 +328,9 @@ export class OrderService { }); } - return ( - await this.orders({ + const [orders, count] = await Promise.all([ + this.orders({ + orderBy, skip, take, where, @@ -332,10 +344,12 @@ export class OrderService { // eslint-disable-next-line @typescript-eslint/naming-convention SymbolProfile: true, tags: true - }, - orderBy: { date: 'asc' } - }) - ) + } + }), + this.prismaService.order.count({ where }) + ]); + + const activities = orders .filter((order) => { return ( withExcludedAccounts || @@ -361,6 +375,8 @@ export class OrderService { ) }; }); + + return { activities, count }; } public async updateOrder({ diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 85e914287..050559c85 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -225,7 +225,7 @@ export class PortfolioService { }): Promise { const userId = await this.getUserId(impersonationId, this.request.user.id); - const activities = await this.orderService.getOrders({ + const { activities } = await this.orderService.getOrders({ filters, userId, types: ['DIVIDEND'], @@ -679,13 +679,13 @@ export class PortfolioService { const user = await this.userService.user({ id: userId }); const userCurrency = this.getUserCurrency(user); - const orders = ( - await this.orderService.getOrders({ - userCurrency, - userId, - withExcludedAccounts: true - }) - ).filter(({ SymbolProfile }) => { + const { activities } = await this.orderService.getOrders({ + userCurrency, + userId, + withExcludedAccounts: true + }); + + const orders = activities.filter(({ SymbolProfile }) => { return ( SymbolProfile.dataSource === aDataSource && SymbolProfile.symbol === aSymbol @@ -1639,18 +1639,18 @@ export class PortfolioService { userId }); - const activities = await this.orderService.getOrders({ + const { activities } = await this.orderService.getOrders({ userCurrency, userId }); - const excludedActivities = ( - await this.orderService.getOrders({ - userCurrency, - userId, - withExcludedAccounts: true - }) - ).filter(({ Account: account }) => { + let { activities: excludedActivities } = await this.orderService.getOrders({ + userCurrency, + userId, + withExcludedAccounts: true + }); + + excludedActivities = excludedActivities.filter(({ Account: account }) => { return account?.isExcluded ?? false; }); @@ -1830,7 +1830,7 @@ export class PortfolioService { const userCurrency = this.request.user?.Settings?.settings.baseCurrency ?? DEFAULT_CURRENCY; - const orders = await this.orderService.getOrders({ + const { activities, count } = await this.orderService.getOrders({ filters, includeDrafts, userCurrency, @@ -1839,11 +1839,11 @@ export class PortfolioService { types: ['BUY', 'SELL'] }); - if (orders.length <= 0) { + if (count <= 0) { return { transactionPoints: [], orders: [], portfolioOrders: [] }; } - const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({ + const portfolioOrders: PortfolioOrder[] = activities.map((order) => ({ currency: order.SymbolProfile.currency, dataSource: order.SymbolProfile.dataSource, date: format(order.date, DATE_FORMAT), @@ -1877,8 +1877,8 @@ export class PortfolioService { portfolioCalculator.computeTransactionPoints(); return { - orders, portfolioOrders, + orders: activities, transactionPoints: portfolioCalculator.getTransactionPoints() }; } @@ -1913,13 +1913,14 @@ export class PortfolioService { userId: string; withExcludedAccounts?: boolean; }) { - const ordersOfTypeItemOrLiability = await this.orderService.getOrders({ - filters, - userCurrency, - userId, - withExcludedAccounts, - types: ['ITEM', 'LIABILITY'] - }); + const { activities: ordersOfTypeItemOrLiability } = + await this.orderService.getOrders({ + filters, + userCurrency, + userId, + withExcludedAccounts, + types: ['ITEM', 'LIABILITY'] + }); const accounts: PortfolioDetails['accounts'] = {}; const platforms: PortfolioDetails['platforms'] = {}; 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 259611c0d..a1f596e37 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 @@ -1,5 +1,7 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; +import { PageEvent } from '@angular/material/paginator'; +import { MatTableDataSource } from '@angular/material/table'; import { ActivatedRoute, Router } from '@angular/router'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; @@ -10,10 +12,11 @@ import { DataService } from '@ghostfolio/client/services/data.service'; import { IcsService } from '@ghostfolio/client/services/ics/ics.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; +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'; @@ -30,12 +33,18 @@ import { ImportActivitiesDialogParams } from './import-activities-dialog/interfa }) export class ActivitiesPageComponent implements OnDestroy, OnInit { public activities: Activity[]; + public dataSource: MatTableDataSource; public defaultAccountId: string; public deviceType: string; public hasImpersonationId: boolean; public hasPermissionToCreateActivity: boolean; public hasPermissionToDeleteActivity: boolean; + public pageIndex = 0; + public pageSize = DEFAULT_PAGE_SIZE; public routeQueryParams: Subscription; + public sortColumn = 'date'; + public sortDirection: Prisma.SortOrder = 'desc'; + public totalItems: number; public user: User; private unsubscribeSubject = new Subject(); @@ -103,21 +112,48 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit { } public fetchActivities() { - this.dataService - .fetchActivities({}) - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ activities }) => { - this.activities = activities; + if (this.user?.settings?.isExperimentalFeatures === true) { + this.dataService + .fetchActivities({ + skip: this.pageIndex * this.pageSize, + sortColumn: this.sortColumn, + sortDirection: this.sortDirection, + take: this.pageSize + }) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ activities, count }) => { + this.dataSource = new MatTableDataSource(activities); + this.totalItems = count; - if ( - this.hasPermissionToCreateActivity && - this.activities?.length <= 0 - ) { - this.router.navigate([], { queryParams: { createDialog: true } }); - } + if (this.hasPermissionToCreateActivity && this.totalItems <= 0) { + this.router.navigate([], { queryParams: { createDialog: true } }); + } - this.changeDetectorRef.markForCheck(); - }); + 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(); + }); + } + } + + public onChangePage(page: PageEvent) { + this.pageIndex = page.pageIndex; + + this.fetchActivities(); } public onCloneActivity(aActivity: Activity) { diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.html b/apps/client/src/app/pages/portfolio/activities/activities-page.html index 8c2cf9bd5..bab5eb066 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.html +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -2,7 +2,30 @@

Activities

+ { - 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, count }) => { + for (const activity of activities) { + activity.createdAt = parseISO(activity.createdAt); + activity.date = parseISO(activity.date); + } + return { activities, count }; }) - .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 new file mode 100644 index 000000000..116883b18 --- /dev/null +++ b/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.html @@ -0,0 +1,499 @@ +
+ + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ +
+
+ +
{{ element.dataSource }}
+
+ Name + +
+
+ {{ element.SymbolProfile?.name }} + Draft +
+
+
+ {{ + element.SymbolProfile?.symbol | gfSymbol + }} +
+
+ Type + + + + Date + +
+ {{ element.date | date: defaultDateFormat }} +
+
+ Quantity + +
+ +
+
+ Unit Price + +
+ +
+
+ Fee + +
+ +
+
+ Value + +
+ +
+
+ Currency + + {{ element.SymbolProfile?.currency }} + + Value + +
+ +
+
+ Account + +
+ + {{ element.Account?.name }} +
+
+ + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ +
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 new file mode 100644 index 000000000..003303f95 --- /dev/null +++ b/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.scss @@ -0,0 +1,9 @@ +@import 'apps/client/src/styles/ghostfolio-style'; + +:host { + display: block; + + .activities { + overflow-x: auto; + } +} 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 new file mode 100644 index 000000000..4300dd421 --- /dev/null +++ b/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.component.ts @@ -0,0 +1,237 @@ +import { SelectionModel } from '@angular/cdk/collections'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + ViewChild +} from '@angular/core'; +import { MatPaginator, PageEvent } from '@angular/material/paginator'; +import { MatTableDataSource } from '@angular/material/table'; +import { Router } from '@angular/router'; +import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; +import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; +import { getDateFormatString } from '@ghostfolio/common/helper'; +import { UniqueAsset } from '@ghostfolio/common/interfaces'; +import { OrderWithAccount } from '@ghostfolio/common/types'; +import { isUUID } from 'class-validator'; +import { endOfToday, isAfter } from 'date-fns'; +import { Subject, Subscription, takeUntil } from 'rxjs'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + selector: 'gf-activities-table-lazy', + styleUrls: ['./activities-table-lazy.component.scss'], + templateUrl: './activities-table-lazy.component.html' +}) +export class ActivitiesTableLazyComponent + implements OnChanges, OnDestroy, OnInit +{ + @Input() baseCurrency: string; + @Input() dataSource: MatTableDataSource; + @Input() deviceType: string; + @Input() hasPermissionToCreateActivity: boolean; + @Input() hasPermissionToExportActivities: boolean; + @Input() hasPermissionToOpenDetails = true; + @Input() locale: string; + @Input() pageIndex: number; + @Input() pageSize = DEFAULT_PAGE_SIZE; + @Input() showActions = true; + @Input() showCheckbox = false; + @Input() showFooter = true; + @Input() showNameColumn = true; + @Input() totalItems = Number.MAX_SAFE_INTEGER; + + @Output() activityDeleted = new EventEmitter(); + @Output() activityToClone = new EventEmitter(); + @Output() activityToUpdate = new EventEmitter(); + @Output() deleteAllActivities = new EventEmitter(); + @Output() export = new EventEmitter(); + @Output() exportDrafts = new EventEmitter(); + @Output() import = new EventEmitter(); + @Output() importDividends = new EventEmitter(); + @Output() pageChanged = new EventEmitter(); + @Output() selectedActivities = new EventEmitter(); + + @ViewChild(MatPaginator) paginator: MatPaginator; + + public defaultDateFormat: string; + public displayedColumns = []; + public endOfToday = endOfToday(); + public hasDrafts = false; + public hasErrors = false; + public isAfter = isAfter; + public isLoading = true; + public isUUID = isUUID; + public routeQueryParams: Subscription; + public searchKeywords: string[] = []; + public selectedRows = new SelectionModel(true, []); + + private unsubscribeSubject = new Subject(); + + public constructor(private router: Router) {} + + public ngOnInit() { + if (this.showCheckbox) { + this.toggleAllRows(); + this.selectedRows.changed + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((selectedRows) => { + this.selectedActivities.emit(selectedRows.source.selected); + }); + } + } + + public areAllRowsSelected() { + const numSelectedRows = this.selectedRows.selected.length; + const numTotalRows = this.dataSource.data.length; + return numSelectedRows === numTotalRows; + } + + public ngOnChanges() { + this.defaultDateFormat = getDateFormatString(this.locale); + + this.displayedColumns = [ + 'select', + 'importStatus', + 'icon', + 'nameWithSymbol', + 'type', + 'date', + 'quantity', + 'unitPrice', + 'fee', + 'value', + 'currency', + 'valueInBaseCurrency', + 'account', + 'comment', + 'actions' + ]; + + if (!this.showCheckbox) { + this.displayedColumns = this.displayedColumns.filter((column) => { + return column !== 'importStatus' && column !== 'select'; + }); + } + + if (!this.showNameColumn) { + this.displayedColumns = this.displayedColumns.filter((column) => { + return column !== 'nameWithSymbol'; + }); + } + + if (this.dataSource) { + this.isLoading = false; + } + } + + public onChangePage(page: PageEvent) { + this.pageChanged.emit(page); + } + + public onClickActivity(activity: Activity) { + if (this.showCheckbox) { + if (!activity.error) { + this.selectedRows.toggle(activity); + } + } else if ( + this.hasPermissionToOpenDetails && + !activity.isDraft && + activity.type !== 'FEE' && + activity.type !== 'INTEREST' && + activity.type !== 'ITEM' && + activity.type !== 'LIABILITY' + ) { + this.onOpenPositionDialog({ + dataSource: activity.SymbolProfile.dataSource, + symbol: activity.SymbolProfile.symbol + }); + } + } + + public onCloneActivity(aActivity: OrderWithAccount) { + this.activityToClone.emit(aActivity); + } + + public onDeleteActivity(aId: string) { + const confirmation = confirm( + $localize`Do you really want to delete this activity?` + ); + + if (confirmation) { + this.activityDeleted.emit(aId); + } + } + + public onExport() { + if (this.searchKeywords.length > 0) { + this.export.emit( + this.dataSource.filteredData.map((activity) => { + return activity.id; + }) + ); + } else { + this.export.emit(); + } + } + + public onExportDraft(aActivityId: string) { + this.exportDrafts.emit([aActivityId]); + } + + public onExportDrafts() { + this.exportDrafts.emit( + this.dataSource.filteredData + .filter((activity) => { + return activity.isDraft; + }) + .map((activity) => { + return activity.id; + }) + ); + } + + public onDeleteAllActivities() { + this.deleteAllActivities.emit(); + } + + public onImport() { + this.import.emit(); + } + + public onImportDividends() { + this.importDividends.emit(); + } + + public onOpenComment(aComment: string) { + alert(aComment); + } + + public onOpenPositionDialog({ dataSource, symbol }: UniqueAsset): void { + this.router.navigate([], { + queryParams: { dataSource, symbol, positionDetailDialog: true } + }); + } + + public onUpdateActivity(aActivity: OrderWithAccount) { + this.activityToUpdate.emit(aActivity); + } + + public toggleAllRows() { + this.areAllRowsSelected() + ? this.selectedRows.clear() + : this.dataSource.data.forEach((row) => this.selectedRows.select(row)); + + this.selectedActivities.emit(this.selectedRows.selected); + } + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); + } +} 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 new file mode 100644 index 000000000..3d7e79a4b --- /dev/null +++ b/libs/ui/src/lib/activities-table-lazy/activities-table-lazy.module.ts @@ -0,0 +1,41 @@ +import { CommonModule } from '@angular/common'; +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +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 { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterModule } from '@angular/router'; +import { GfSymbolIconModule } from '@ghostfolio/client/components/symbol-icon/symbol-icon.module'; +import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; +import { GfActivityTypeModule } from '@ghostfolio/ui/activity-type'; +import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info'; +import { GfValueModule } from '@ghostfolio/ui/value'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; + +import { ActivitiesTableLazyComponent } from './activities-table-lazy.component'; + +@NgModule({ + declarations: [ActivitiesTableLazyComponent], + exports: [ActivitiesTableLazyComponent], + imports: [ + CommonModule, + GfActivityTypeModule, + GfNoTransactionsInfoModule, + GfSymbolIconModule, + GfSymbolModule, + GfValueModule, + MatButtonModule, + MatCheckboxModule, + MatMenuModule, + MatPaginatorModule, + MatTableModule, + MatTooltipModule, + NgxSkeletonLoaderModule, + RouterModule + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class GfActivitiesTableLazyModule {} From 2c7ece50fe9d141034168fb3a0366ec28bd4cbe2 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 9 Dec 2023 17:14:13 +0100 Subject: [PATCH 3/5] Release 2.29.0 (#2733) --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 439767581..94b7a42f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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 +## 2.29.0 - 2023-12-09 ### Added diff --git a/package.json b/package.json index bccedc68f..e961428a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.28.0", + "version": "2.29.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From a768902b004ab61fa268fc0c3e234c23ce62347d Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 9 Dec 2023 17:24:25 +0100 Subject: [PATCH 4/5] Feature/update prisma to version 5.7.0 (#2734) * Update prisma to version 5.7.0 * Update changelog --- CHANGELOG.md | 6 +++++ package.json | 4 ++-- yarn.lock | 66 +++++++++++++++++++++++++++++++++++----------------- 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94b7a42f1..dcecac253 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 + +### Changed + +- Upgraded `prisma` from version `5.6.0` to `5.7.0` + ## 2.29.0 - 2023-12-09 ### Added diff --git a/package.json b/package.json index e961428a7..1770499a4 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "@nestjs/platform-express": "10.1.3", "@nestjs/schedule": "3.0.2", "@nestjs/serve-static": "4.0.0", - "@prisma/client": "5.6.0", + "@prisma/client": "5.7.0", "@simplewebauthn/browser": "8.3.1", "@simplewebauthn/server": "8.3.2", "@stripe/stripe-js": "1.47.0", @@ -122,7 +122,7 @@ "passport": "0.6.0", "passport-google-oauth20": "2.0.0", "passport-jwt": "4.0.0", - "prisma": "5.6.0", + "prisma": "5.7.0", "reflect-metadata": "0.1.13", "rxjs": "7.5.6", "stripe": "11.12.0", diff --git a/yarn.lock b/yarn.lock index c19ae8175..33f77c7c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4092,22 +4092,46 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@prisma/client@5.6.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.6.0.tgz#1c15932250d5658fe0127e62faf4ecd96a877259" - integrity sha512-mUDefQFa1wWqk4+JhKPYq8BdVoFk9NFMBXUI8jAkBfQTtgx8WPx02U2HB/XbAz3GSUJpeJOKJQtNvaAIDs6sug== - dependencies: - "@prisma/engines-version" "5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee" - -"@prisma/engines-version@5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee": - version "5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee.tgz#57b003ab5e1ea1523b5cdd7f06b24ebcf5c7fd8c" - integrity sha512-UoFgbV1awGL/3wXuUK3GDaX2SolqczeeJ5b4FVec9tzeGbSWJboPSbT0psSrmgYAKiKnkOPFSLlH6+b+IyOwAw== - -"@prisma/engines@5.6.0": - version "5.6.0" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.6.0.tgz#82c445aa10633bbc0388aa2d6e411a0bd94c9439" - integrity sha512-Mt2q+GNJpU2vFn6kif24oRSBQv1KOkYaterQsi0k2/lA+dLvhRX6Lm26gon6PYHwUM8/h8KRgXIUMU0PCLB6bw== +"@prisma/client@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.7.0.tgz#c29dd9a16e100902eb2d2443d90fee2482d2aeac" + integrity sha512-cZmglCrfNbYpzUtz7HscVHl38e9CrUs31nrVoGUK1nIPXGgt8hT4jj2s657UXcNdQ/jBUxDgGmHyu2Nyrq1txg== + +"@prisma/debug@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.7.0.tgz#abdb2060be4fe819e73e2683cf1b039841566198" + integrity sha512-tZ+MOjWlVvz1kOEhNYMa4QUGURY+kgOUBqLHYIV8jmCsMuvA1tWcn7qtIMLzYWCbDcQT4ZS8xDgK0R2gl6/0wA== + +"@prisma/engines-version@5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9": + version "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9.tgz#777827898f1bfe6a76b17fbe7d9600cf543c4cc1" + integrity sha512-V6tgRVi62jRwTm0Hglky3Scwjr/AKFBFtS+MdbsBr7UOuiu1TKLPc6xfPiyEN1+bYqjEtjxwGsHgahcJsd1rNg== + +"@prisma/engines@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.7.0.tgz#a32e232819b66bd9dee7500b455781742dc54b2f" + integrity sha512-TkOMgMm60n5YgEKPn9erIvFX2/QuWnl3GBo6yTRyZKk5O5KQertXiNnrYgSLy0SpsKmhovEPQb+D4l0SzyE7XA== + dependencies: + "@prisma/debug" "5.7.0" + "@prisma/engines-version" "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9" + "@prisma/fetch-engine" "5.7.0" + "@prisma/get-platform" "5.7.0" + +"@prisma/fetch-engine@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.7.0.tgz#7d2795828b692b02707e7ab6876f6227a68fc309" + integrity sha512-zIn/qmO+N/3FYe7/L9o+yZseIU8ivh4NdPKSkQRIHfg2QVTVMnbhGoTcecbxfVubeTp+DjcbjS0H9fCuM4W04w== + dependencies: + "@prisma/debug" "5.7.0" + "@prisma/engines-version" "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9" + "@prisma/get-platform" "5.7.0" + +"@prisma/get-platform@5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.7.0.tgz#eb81011f537c2d10c0225278cd5165a82d0b57c8" + integrity sha512-ZeV/Op4bZsWXuw5Tg05WwRI8BlKiRFhsixPcAM+5BKYSiUZiMKIi713tfT3drBq8+T0E1arNZgYSA9QYcglWNA== + dependencies: + "@prisma/debug" "5.7.0" "@radix-ui/number@1.0.1": version "1.0.1" @@ -16120,12 +16144,12 @@ pretty-hrtime@^1.0.3: resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A== -prisma@5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.6.0.tgz#ae2c27fdfb4d53be7f7dafb50d6b8b7f55c93aa5" - integrity sha512-EEaccku4ZGshdr2cthYHhf7iyvCcXqwJDvnoQRAJg5ge2Tzpv0e2BaMCp+CbbDUwoVTzwgOap9Zp+d4jFa2O9A== +prisma@5.7.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.7.0.tgz#3c1c56d392b5d1137de954edefa4533fa092663e" + integrity sha512-0rcfXO2ErmGAtxnuTNHQT9ztL0zZheQjOI/VNJzdq87C3TlGPQtMqtM+KCwU6XtmkoEr7vbCQqA7HF9IY0ST+Q== dependencies: - "@prisma/engines" "5.6.0" + "@prisma/engines" "5.7.0" prismjs@^1.28.0: version "1.29.0" From 25424ad280709c15554de692f04daccb631f5f16 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 10 Dec 2023 14:50:32 +0100 Subject: [PATCH 5/5] Feature/update prisma to version 5.7.0 (#2737) * Update prisma to version 5.7.0 * Update changelog