diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a5a5dc5b..ff0111ba7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +- Set the actions columns of various tables to stick at the end +- Increased the height of the tabs on mobile +- Improved the language localization for German (`de`) +- Improved the language localization for Türkçe (`tr`) +- Upgraded `marked` from version `4.2.12` to `9.1.6` +- Upgraded `ngx-markdown` from version `15.1.0` to `17.1.1` +- Upgraded `ng-extract-i18n-merge` from version `2.8.3` to `2.9.0` + +### Fixed + +- Fixed an issue in the biometric authentication registration + +## 2.28.0 - 2023-12-02 + ### Added - Added a historical cash balances table to the account detail dialog @@ -14,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Relaxed the check for duplicates in the preview step of the activities import (allow same day) - Respected the `withExcludedAccounts` flag in the account balance time series ### Fixed @@ -170,7 +187,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Improved the check for duplicates in the preview step of the activities import (allow different accounts) +- Relaxed the check for duplicates in the preview step of the activities import (allow different accounts) - Improved the usability and validation in the cash balance transfer from one to another account - Changed the checkboxes to slide toggles in the overview of the admin control panel - Switched from the deprecated (`PUT`) to the new endpoint (`POST`) to manage historical market data in the asset profile details dialog of the admin control panel diff --git a/apps/api/src/app/account-balance/account-balance.controller.ts b/apps/api/src/app/account-balance/account-balance.controller.ts new file mode 100644 index 000000000..f1538d7a5 --- /dev/null +++ b/apps/api/src/app/account-balance/account-balance.controller.ts @@ -0,0 +1,51 @@ +import type { RequestWithUser } from '@ghostfolio/common/types'; +import { + Controller, + Delete, + HttpException, + Inject, + Param, + UseGuards +} from '@nestjs/common'; +import { REQUEST } from '@nestjs/core'; +import { AccountBalanceService } from './account-balance.service'; +import { AuthGuard } from '@nestjs/passport'; +import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { StatusCodes, getReasonPhrase } from 'http-status-codes'; +import { AccountBalance } from '@prisma/client'; + +@Controller('account-balance') +export class AccountBalanceController { + public constructor( + private readonly accountBalanceService: AccountBalanceService, + @Inject(REQUEST) private readonly request: RequestWithUser + ) {} + + @Delete(':id') + @UseGuards(AuthGuard('jwt')) + public async deleteAccountBalance( + @Param('id') id: string + ): Promise { + const accountBalance = await this.accountBalanceService.accountBalance({ + id + }); + + if ( + !hasPermission( + this.request.user.permissions, + permissions.deleteAccountBalance + ) || + !accountBalance || + accountBalance.userId !== this.request.user.id + ) { + throw new HttpException( + getReasonPhrase(StatusCodes.FORBIDDEN), + StatusCodes.FORBIDDEN + ); + } + + return this.accountBalanceService.deleteAccountBalance({ + id + }); + } +} diff --git a/apps/api/src/services/account-balance/account-balance.module.ts b/apps/api/src/app/account-balance/account-balance.module.ts similarity index 68% rename from apps/api/src/services/account-balance/account-balance.module.ts rename to apps/api/src/app/account-balance/account-balance.module.ts index c85727f8c..d78d9792e 100644 --- a/apps/api/src/services/account-balance/account-balance.module.ts +++ b/apps/api/src/app/account-balance/account-balance.module.ts @@ -1,9 +1,12 @@ -import { AccountBalanceService } from '@ghostfolio/api/services/account-balance/account-balance.service'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { Module } from '@nestjs/common'; +import { AccountBalanceController } from './account-balance.controller'; +import { AccountBalanceService } from './account-balance.service'; + @Module({ + controllers: [AccountBalanceController], exports: [AccountBalanceService], imports: [ExchangeRateDataModule, PrismaModule], providers: [AccountBalanceService] diff --git a/apps/api/src/services/account-balance/account-balance.service.ts b/apps/api/src/app/account-balance/account-balance.service.ts similarity index 80% rename from apps/api/src/services/account-balance/account-balance.service.ts rename to apps/api/src/app/account-balance/account-balance.service.ts index e1d002428..0845eda5a 100644 --- a/apps/api/src/services/account-balance/account-balance.service.ts +++ b/apps/api/src/app/account-balance/account-balance.service.ts @@ -12,6 +12,17 @@ export class AccountBalanceService { private readonly prismaService: PrismaService ) {} + public async accountBalance( + accountBalanceWhereInput: Prisma.AccountBalanceWhereInput + ): Promise { + return this.prismaService.accountBalance.findFirst({ + include: { + Account: true + }, + where: accountBalanceWhereInput + }); + } + public async createAccountBalance( data: Prisma.AccountBalanceCreateInput ): Promise { @@ -20,6 +31,14 @@ export class AccountBalanceService { }); } + public async deleteAccountBalance( + where: Prisma.AccountBalanceWhereUniqueInput + ): Promise { + return this.prismaService.accountBalance.delete({ + where + }); + } + public async getAccountBalances({ filters, user, diff --git a/apps/api/src/app/account/account.controller.ts b/apps/api/src/app/account/account.controller.ts index 3eeb7117c..772a66e4c 100644 --- a/apps/api/src/app/account/account.controller.ts +++ b/apps/api/src/app/account/account.controller.ts @@ -1,6 +1,6 @@ +import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor'; -import { AccountBalanceService } from '@ghostfolio/api/services/account-balance/account-balance.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; import { diff --git a/apps/api/src/app/account/account.module.ts b/apps/api/src/app/account/account.module.ts index 26ace47c2..a8fb7e848 100644 --- a/apps/api/src/app/account/account.module.ts +++ b/apps/api/src/app/account/account.module.ts @@ -1,7 +1,7 @@ +import { AccountBalanceModule } from '@ghostfolio/api/app/account-balance/account-balance.module'; import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { UserModule } from '@ghostfolio/api/app/user/user.module'; -import { AccountBalanceModule } from '@ghostfolio/api/services/account-balance/account-balance.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; diff --git a/apps/api/src/app/account/account.service.ts b/apps/api/src/app/account/account.service.ts index bc6abcc7a..366d0b1a0 100644 --- a/apps/api/src/app/account/account.service.ts +++ b/apps/api/src/app/account/account.service.ts @@ -1,4 +1,4 @@ -import { AccountBalanceService } from '@ghostfolio/api/services/account-balance/account-balance.service'; +import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { Filter } from '@ghostfolio/common/interfaces'; diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index eb556421d..f89c57770 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -26,7 +26,7 @@ import { import { Injectable } from '@nestjs/common'; import { DataSource, Prisma, SymbolProfile } from '@prisma/client'; import Big from 'big.js'; -import { endOfToday, format, isAfter, isSameDay, parseISO } from 'date-fns'; +import { endOfToday, format, isAfter, isSameSecond, parseISO } from 'date-fns'; import { uniqBy } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; @@ -83,12 +83,13 @@ export class ImportService { const value = new Big(quantity).mul(marketPrice).toNumber(); + const date = parseDate(dateString); const isDuplicate = orders.some((activity) => { return ( activity.accountId === Account?.id && activity.SymbolProfile.currency === assetProfile.currency && activity.SymbolProfile.dataSource === assetProfile.dataSource && - isSameDay(activity.date, parseDate(dateString)) && + isSameSecond(activity.date, date) && activity.quantity === quantity && activity.SymbolProfile.symbol === assetProfile.symbol && activity.type === 'DIVIDEND' && @@ -102,6 +103,7 @@ export class ImportService { return { Account, + date, error, quantity, value, @@ -109,7 +111,6 @@ export class ImportService { accountUserId: undefined, comment: undefined, createdAt: undefined, - date: parseDate(dateString), fee: 0, feeInBaseCurrency: 0, id: assetProfile.id, @@ -482,13 +483,13 @@ export class ImportService { type, unitPrice }) => { - const date = parseISO((dateString)); + const date = parseISO(dateString); const isDuplicate = existingActivities.some((activity) => { return ( activity.accountId === accountId && activity.SymbolProfile.currency === currency && activity.SymbolProfile.dataSource === dataSource && - isSameDay(activity.date, date) && + isSameSecond(activity.date, date) && activity.fee === fee && activity.quantity === quantity && activity.SymbolProfile.symbol === symbol && diff --git a/apps/api/src/app/order/order.module.ts b/apps/api/src/app/order/order.module.ts index 8f033058d..53d69c0f9 100644 --- a/apps/api/src/app/order/order.module.ts +++ b/apps/api/src/app/order/order.module.ts @@ -1,8 +1,8 @@ +import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { CacheModule } from '@ghostfolio/api/app/cache/cache.module'; import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { UserModule } from '@ghostfolio/api/app/user/user.module'; -import { AccountBalanceService } from '@ghostfolio/api/services/account-balance/account-balance.service'; import { ApiModule } from '@ghostfolio/api/services/api/api.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module'; diff --git a/apps/api/src/app/portfolio/portfolio.module.ts b/apps/api/src/app/portfolio/portfolio.module.ts index 3b4ee5d76..cf3dd2490 100644 --- a/apps/api/src/app/portfolio/portfolio.module.ts +++ b/apps/api/src/app/portfolio/portfolio.module.ts @@ -1,8 +1,8 @@ import { AccessModule } from '@ghostfolio/api/app/access/access.module'; +import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { OrderModule } from '@ghostfolio/api/app/order/order.module'; import { UserModule } from '@ghostfolio/api/app/user/user.module'; -import { AccountBalanceService } from '@ghostfolio/api/services/account-balance/account-balance.service'; import { ApiModule } from '@ghostfolio/api/services/api/api.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module'; diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 1f701c040..9bc585397 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -1,3 +1,4 @@ +import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; @@ -12,7 +13,6 @@ import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from '@ghostfolio/ap import { CurrencyClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/current-investment'; import { EmergencyFundSetup } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-setup'; import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment'; -import { AccountBalanceService } from '@ghostfolio/api/services/account-balance/account-balance.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; diff --git a/apps/client/src/app/components/access-table/access-table.component.html b/apps/client/src/app/components/access-table/access-table.component.html index 589c67e1f..e9138717d 100644 --- a/apps/client/src/app/components/access-table/access-table.component.html +++ b/apps/client/src/app/components/access-table/access-table.component.html @@ -37,7 +37,7 @@ - + diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts index b3a916da9..aa835b00f 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts @@ -11,7 +11,11 @@ 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'; import { downloadAsFile } from '@ghostfolio/common/helper'; -import { HistoricalDataItem, User } from '@ghostfolio/common/interfaces'; +import { + AccountBalancesResponse, + HistoricalDataItem, + User +} from '@ghostfolio/common/interfaces'; import { OrderWithAccount } from '@ghostfolio/common/types'; import Big from 'big.js'; import { format, parseISO } from 'date-fns'; @@ -20,6 +24,7 @@ import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { AccountDetailDialogParams } from './interfaces/interfaces'; +import { hasPermission, permissions } from '@ghostfolio/common/permissions'; @Component({ host: { class: 'd-flex flex-column h-100' }, @@ -29,11 +34,13 @@ import { AccountDetailDialogParams } from './interfaces/interfaces'; styleUrls: ['./account-detail-dialog.component.scss'] }) export class AccountDetailDialog implements OnDestroy, OnInit { + public accountBalances: AccountBalancesResponse['balances']; public activities: OrderWithAccount[]; public balance: number; public currency: string; public equity: number; public hasImpersonationId: boolean; + public hasPermissionToDeleteAccountBalance: boolean; public historicalDataItems: HistoricalDataItem[]; public isLoadingActivities: boolean; public isLoadingChart: boolean; @@ -59,6 +66,11 @@ export class AccountDetailDialog implements OnDestroy, OnInit { if (state?.user) { this.user = state.user; + this.hasPermissionToDeleteAccountBalance = hasPermission( + this.user.permissions, + permissions.deleteAccountBalance + ); + this.changeDetectorRef.markForCheck(); } }); @@ -66,7 +78,6 @@ export class AccountDetailDialog implements OnDestroy, OnInit { public ngOnInit() { this.isLoadingActivities = true; - this.isLoadingChart = true; this.dataService .fetchAccount(this.data.accountId) @@ -112,48 +123,33 @@ export class AccountDetailDialog implements OnDestroy, OnInit { this.changeDetectorRef.markForCheck(); }); - this.dataService - .fetchPortfolioPerformance({ - filters: [ - { - id: this.data.accountId, - type: 'ACCOUNT' - } - ], - range: 'max', - withExcludedAccounts: true - }) - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ chart }) => { - this.historicalDataItems = chart.map( - ({ date, netWorth, netWorthInPercentage }) => { - return { - date, - value: - this.hasImpersonationId || this.user.settings.isRestrictedView - ? netWorthInPercentage - : netWorth - }; - } - ); - - this.isLoadingChart = false; - - this.changeDetectorRef.markForCheck(); - }); - this.impersonationStorageService .onChangeHasImpersonation() .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((impersonationId) => { this.hasImpersonationId = !!impersonationId; }); + + this.fetchAccountBalances(); + this.fetchPortfolioPerformance(); } public onClose() { this.dialogRef.close(); } + public onDeleteAccountBalance(aId: string) { + this.dataService + .deleteAccountBalance(aId) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe({ + next: () => { + this.fetchAccountBalances(); + this.fetchPortfolioPerformance(); + } + }); + } + public onExport() { this.dataService .fetchExport( @@ -176,6 +172,51 @@ export class AccountDetailDialog implements OnDestroy, OnInit { }); } + private fetchAccountBalances() { + this.dataService + .fetchAccountBalances(this.data.accountId) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ balances }) => { + this.accountBalances = balances; + + this.changeDetectorRef.markForCheck(); + }); + } + + private fetchPortfolioPerformance() { + this.isLoadingChart = true; + + this.dataService + .fetchPortfolioPerformance({ + filters: [ + { + id: this.data.accountId, + type: 'ACCOUNT' + } + ], + range: 'max', + withExcludedAccounts: true + }) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ chart }) => { + this.historicalDataItems = chart.map( + ({ date, netWorth, netWorthInPercentage }) => { + return { + date, + value: + this.hasImpersonationId || this.user.settings.isRestrictedView + ? netWorthInPercentage + : netWorth + }; + } + ); + + this.isLoadingChart = false; + + this.changeDetectorRef.markForCheck(); + }); + } + public ngOnDestroy() { this.unsubscribeSubject.next(); this.unsubscribeSubject.complete(); diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html index 7e92eca85..647ba0d6f 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html @@ -87,8 +87,11 @@ Cash Balances diff --git a/apps/client/src/app/components/accounts-table/accounts-table.component.html b/apps/client/src/app/components/accounts-table/accounts-table.component.html index b8c9ac4f7..97a87b716 100644 --- a/apps/client/src/app/components/accounts-table/accounts-table.component.html +++ b/apps/client/src/app/components/accounts-table/accounts-table.component.html @@ -241,7 +241,7 @@ > - + + + + + + + diff --git a/libs/ui/src/lib/account-balances/account-balances.component.ts b/libs/ui/src/lib/account-balances/account-balances.component.ts index c552519d6..4bcf7b26a 100644 --- a/libs/ui/src/lib/account-balances/account-balances.component.ts +++ b/libs/ui/src/lib/account-balances/account-balances.component.ts @@ -1,18 +1,19 @@ import { ChangeDetectionStrategy, - ChangeDetectorRef, Component, + EventEmitter, Input, + OnChanges, OnDestroy, OnInit, + Output, ViewChild } from '@angular/core'; import { MatSort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; -import { DataService } from '@ghostfolio/client/services/data.service'; import { AccountBalancesResponse } from '@ghostfolio/common/interfaces'; import { get } from 'lodash'; -import { Subject, takeUntil } from 'rxjs'; +import { Subject } from 'rxjs'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -20,44 +21,48 @@ import { Subject, takeUntil } from 'rxjs'; styleUrls: ['./account-balances.component.scss'], templateUrl: './account-balances.component.html' }) -export class AccountBalancesComponent implements OnDestroy, OnInit { +export class AccountBalancesComponent implements OnChanges, OnDestroy, OnInit { + @Input() accountBalances: AccountBalancesResponse['balances']; @Input() accountId: string; @Input() locale: string; + @Input() showActions = true; + + @Output() accountBalanceDeleted = new EventEmitter(); @ViewChild(MatSort) sort: MatSort; public dataSource: MatTableDataSource< AccountBalancesResponse['balances'][0] > = new MatTableDataSource(); - public displayedColumns: string[] = ['date', 'value']; + public displayedColumns: string[] = ['date', 'value', 'actions']; private unsubscribeSubject = new Subject(); - public constructor( - private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService - ) {} + public constructor() {} - public ngOnInit() { - this.fetchBalances(); - } + public ngOnInit() {} - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); + public ngOnChanges() { + if (this.accountBalances) { + this.dataSource = new MatTableDataSource(this.accountBalances); + + this.dataSource.sort = this.sort; + this.dataSource.sortingDataAccessor = get; + } } - private fetchBalances() { - this.dataService - .fetchAccountBalances(this.accountId) - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ balances }) => { - this.dataSource = new MatTableDataSource(balances); + public onDeleteAccountBalance(aId: string) { + const confirmation = confirm( + $localize`Do you really want to delete this account balance?` + ); - this.dataSource.sort = this.sort; - this.dataSource.sortingDataAccessor = get; + if (confirmation) { + this.accountBalanceDeleted.emit(aId); + } + } - this.changeDetectorRef.markForCheck(); - }); + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); } } diff --git a/libs/ui/src/lib/account-balances/account-balances.module.ts b/libs/ui/src/lib/account-balances/account-balances.module.ts index cc8fb9677..210151cb2 100644 --- a/libs/ui/src/lib/account-balances/account-balances.module.ts +++ b/libs/ui/src/lib/account-balances/account-balances.module.ts @@ -1,5 +1,7 @@ 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 { MatSortModule } from '@angular/material/sort'; import { MatTableModule } from '@angular/material/table'; import { GfValueModule } from '@ghostfolio/ui/value'; @@ -9,7 +11,14 @@ import { AccountBalancesComponent } from './account-balances.component'; @NgModule({ declarations: [AccountBalancesComponent], exports: [AccountBalancesComponent], - imports: [CommonModule, GfValueModule, MatSortModule, MatTableModule], + imports: [ + CommonModule, + GfValueModule, + MatButtonModule, + MatMenuModule, + MatSortModule, + MatTableModule + ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class GfAccountBalancesModule {} 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 d6a5e05af..18d918006 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -428,7 +428,7 @@ > - +