@if (hasPermissionToFilterByType) {
diff --git a/libs/ui/src/lib/activities-table/activities-table.component.scss b/libs/ui/src/lib/activities-table/activities-table.component.scss
index 5d4e87f30f..4e98f726e2 100644
--- a/libs/ui/src/lib/activities-table/activities-table.component.scss
+++ b/libs/ui/src/lib/activities-table/activities-table.component.scss
@@ -1,3 +1,12 @@
+@use '@angular/material' as mat;
+
:host {
display: block;
+
+ @include mat.form-field-overrides(
+ (
+ container-height: 2rem,
+ container-vertical-padding: 0.33rem
+ )
+ );
}
diff --git a/libs/ui/src/lib/activities-table/activities-table.component.stories.ts b/libs/ui/src/lib/activities-table/activities-table.component.stories.ts
index 57a8f18b68..057747cf14 100644
--- a/libs/ui/src/lib/activities-table/activities-table.component.stories.ts
+++ b/libs/ui/src/lib/activities-table/activities-table.component.stories.ts
@@ -494,3 +494,28 @@ export const Actions: Story = {
totalItems: activities.length
}
};
+
+export const Toolbar: Story = {
+ args: {
+ dataSource,
+ baseCurrency: 'USD',
+ deviceType: 'desktop',
+ hasActivities: true,
+ hasPermissionToCreateActivity: true,
+ hasPermissionToDeleteActivity: true,
+ hasPermissionToExportActivities: true,
+ hasPermissionToFilterByType: true,
+ hasPermissionToOpenDetails: false,
+ locale: 'en-US',
+ pageIndex: 0,
+ pageSize: 10,
+ showAccountColumn: true,
+ showActions: false,
+ showCheckbox: false,
+ showNameColumn: true,
+ sortColumn: 'date',
+ sortDirection: 'desc',
+ sortDisabled: false,
+ totalItems: activities.length
+ }
+};
From df2e96fc6f8447c6e0dc9081e01ee8edb44dba67 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Fri, 31 Jul 2026 18:20:57 +0200
Subject: [PATCH 4/7] Task/improve performance of property service by caching
properties (#7484)
* Improve performance by caching properties in memory
* Update changelog
---
CHANGELOG.md | 1 +
apps/api/src/app/admin/admin.service.ts | 7 +-
apps/api/src/app/health/health.service.ts | 4 +-
.../subscription/subscription.controller.ts | 4 +-
.../services/benchmark/benchmark.service.ts | 6 +-
.../src/services/property/property.service.ts | 70 +++++++++++++++++--
6 files changed, 80 insertions(+), 12 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 477048f594..d5c6481e3a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improved the style of the empty state in the _Fear & Greed Index_ component
- Improved the style of the type filter in the activities table component (experimental)
+- Improved the performance of the property service by caching the properties in memory
- Improved the language localization for German (`de`)
## 3.37.0 - 2026-07-30
diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts
index 26a4e06f47..4c608e0fd2 100644
--- a/apps/api/src/app/admin/admin.service.ts
+++ b/apps/api/src/app/admin/admin.service.ts
@@ -107,8 +107,11 @@ export class AdminService {
await this.marketDataService.deleteMany({ dataSource, symbol });
const currency = getCurrencyFromSymbol(symbol);
- const customCurrencies =
- await this.propertyService.getByKey(PROPERTY_CURRENCIES);
+
+ const customCurrencies = await this.propertyService.getByKey(
+ PROPERTY_CURRENCIES,
+ { skipCache: true }
+ );
if (customCurrencies.includes(currency)) {
const updatedCustomCurrencies = customCurrencies.filter(
diff --git a/apps/api/src/app/health/health.service.ts b/apps/api/src/app/health/health.service.ts
index f08f33a1e3..42a0be61b8 100644
--- a/apps/api/src/app/health/health.service.ts
+++ b/apps/api/src/app/health/health.service.ts
@@ -26,7 +26,9 @@ export class HealthService {
public async isDatabaseHealthy() {
try {
- await this.propertyService.getByKey(PROPERTY_CURRENCIES);
+ await this.propertyService.getByKey(PROPERTY_CURRENCIES, {
+ skipCache: true
+ });
return true;
} catch {
diff --git a/apps/api/src/app/subscription/subscription.controller.ts b/apps/api/src/app/subscription/subscription.controller.ts
index 4018e4753e..a70fe87916 100644
--- a/apps/api/src/app/subscription/subscription.controller.ts
+++ b/apps/api/src/app/subscription/subscription.controller.ts
@@ -54,7 +54,9 @@ export class SubscriptionController {
}
let coupons =
- (await this.propertyService.getByKey(PROPERTY_COUPONS)) ?? [];
+ (await this.propertyService.getByKey(PROPERTY_COUPONS, {
+ skipCache: true
+ })) ?? [];
const coupon = coupons.find((currentCoupon) => {
return currentCoupon.code === couponCode;
diff --git a/apps/api/src/services/benchmark/benchmark.service.ts b/apps/api/src/services/benchmark/benchmark.service.ts
index 17e729f9f4..993e0f0aae 100644
--- a/apps/api/src/services/benchmark/benchmark.service.ts
+++ b/apps/api/src/services/benchmark/benchmark.service.ts
@@ -159,7 +159,8 @@ export class BenchmarkService {
let benchmarks =
(await this.propertyService.getByKey(
- PROPERTY_BENCHMARKS
+ PROPERTY_BENCHMARKS,
+ { skipCache: true }
)) ?? [];
benchmarks.push({ symbolProfileId: assetProfile.id });
@@ -196,7 +197,8 @@ export class BenchmarkService {
let benchmarks =
(await this.propertyService.getByKey(
- PROPERTY_BENCHMARKS
+ PROPERTY_BENCHMARKS,
+ { skipCache: true }
)) ?? [];
benchmarks = benchmarks.filter(({ symbolProfileId }) => {
diff --git a/apps/api/src/services/property/property.service.ts b/apps/api/src/services/property/property.service.ts
index 80643482fa..6d8130bbc2 100644
--- a/apps/api/src/services/property/property.service.ts
+++ b/apps/api/src/services/property/property.service.ts
@@ -6,27 +6,39 @@ import {
import { PropertyKey } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
+import { Property } from '@prisma/client';
+import { addMilliseconds, isBefore } from 'date-fns';
+import ms from 'ms';
import { PropertyValue } from './interfaces/interfaces';
@Injectable()
export class PropertyService {
+ private static readonly CACHE_TTL = ms('1 minute');
+
+ private cachedProperties: Promise;
+ private cachedPropertiesExpiresAt: Date;
+
public constructor(private readonly prismaService: PrismaService) {}
public async delete({ key }: { key: PropertyKey }) {
- return this.prismaService.property.delete({
+ const property = await this.prismaService.property.delete({
where: { key }
});
+
+ this.invalidateCache();
+
+ return property;
}
- public async get() {
+ public async get({ skipCache = false } = {}) {
const response: {
[key: string]: PropertyValue;
} = {
[PROPERTY_CURRENCIES]: []
};
- const properties = await this.prismaService.property.findMany();
+ const properties = await this.getProperties({ skipCache });
for (const property of properties) {
let value = property.value;
@@ -41,8 +53,11 @@ export class PropertyService {
return response;
}
- public async getByKey(aKey: PropertyKey) {
- const properties = await this.get();
+ public async getByKey(
+ aKey: PropertyKey,
+ { skipCache = false } = {}
+ ) {
+ const properties = await this.get({ skipCache });
return properties[aKey] as TValue;
}
@@ -53,10 +68,53 @@ export class PropertyService {
}
public async put({ key, value }: { key: PropertyKey; value: string }) {
- return this.prismaService.property.upsert({
+ const property = await this.prismaService.property.upsert({
create: { key, value },
update: { value },
where: { key }
});
+
+ this.invalidateCache();
+
+ return property;
+ }
+
+ /**
+ * Returns the properties from the in-memory cache, falling back to the
+ * database. Callers which write back a modified property must set
+ * skipCache to avoid basing the write on a stale read.
+ */
+ private async getProperties({ skipCache = false } = {}) {
+ if (skipCache) {
+ return this.prismaService.property.findMany();
+ }
+
+ if (
+ this.cachedProperties &&
+ isBefore(new Date(), this.cachedPropertiesExpiresAt)
+ ) {
+ return this.cachedProperties;
+ }
+
+ const properties = this.prismaService.property.findMany().catch((error) => {
+ if (this.cachedProperties === properties) {
+ this.invalidateCache();
+ }
+
+ throw error;
+ });
+
+ this.cachedProperties = properties;
+ this.cachedPropertiesExpiresAt = addMilliseconds(
+ new Date(),
+ PropertyService.CACHE_TTL
+ );
+
+ return this.cachedProperties;
+ }
+
+ private invalidateCache() {
+ this.cachedProperties = undefined;
+ this.cachedPropertiesExpiresAt = undefined;
}
}
From 1770350516c05dddf817f6d727dcdb409f8a873f Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Fri, 31 Jul 2026 18:21:48 +0200
Subject: [PATCH 5/7] Bugfix/static portfolio analysis rules for portfolio with
no holdings (#7466)
* Fix static portfolio analysis rules for portfolio with no holdings
* Update changelog
---
CHANGELOG.md | 14 ++
.../src/app/portfolio/portfolio.service.ts | 208 +++++++++---------
2 files changed, 117 insertions(+), 105 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d5c6481e3a..d14f77f5e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improved the performance of the property service by caching the properties in memory
- Improved the language localization for German (`de`)
+### Fixed
+
+- Fixed the static portfolio analysis rule for a portfolio with no holdings: _Asset Class Cluster Risks_ (Equity)
+- Fixed the static portfolio analysis rule for a portfolio with no holdings: _Asset Class Cluster Risks_ (Fixed Income)
+- Fixed the static portfolio analysis rule for a portfolio with no holdings: _Currency Cluster Risks_ (Investment)
+- Fixed the static portfolio analysis rule for a portfolio with no holdings: _Currency Cluster Risks_ (Investment: Base Currency)
+- Fixed the static portfolio analysis rule for a portfolio with no holdings: _Economic Market Cluster Risks_ (Developed Markets)
+- Fixed the static portfolio analysis rule for a portfolio with no holdings: _Economic Market Cluster Risks_ (Emerging Markets)
+- Fixed the static portfolio analysis rule for a portfolio with no holdings: _Regional Market Cluster Risks_ (Asia-Pacific)
+- Fixed the static portfolio analysis rule for a portfolio with no holdings: _Regional Market Cluster Risks_ (Emerging Markets)
+- Fixed the static portfolio analysis rule for a portfolio with no holdings: _Regional Market Cluster Risks_ (Europe)
+- Fixed the static portfolio analysis rule for a portfolio with no holdings: _Regional Market Cluster Risks_ (Japan)
+- Fixed the static portfolio analysis rule for a portfolio with no holdings: _Regional Market Cluster Risks_ (North America)
+
## 3.37.0 - 2026-07-30
### Added
diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts
index 6a3c1f145a..d1aff38111 100644
--- a/apps/api/src/app/portfolio/portfolio.service.ts
+++ b/apps/api/src/app/portfolio/portfolio.service.ts
@@ -1126,6 +1126,8 @@ export class PortfolioService {
withSummary: true
});
+ const hasOpenHoldings = Object.keys(holdings).length > 0;
+
const marketsAdvancedTotalInBaseCurrency = getSum(
Object.values(marketsAdvanced).map(({ valueInBaseCurrency }) => {
return new Big(valueInBaseCurrency);
@@ -1185,26 +1187,25 @@ export class PortfolioService {
id: 'rule.currencyClusterRisk.category',
languageCode: userSettings.language
}),
- rules:
- summary.activityCount > 0
- ? await this.rulesService.evaluate(
- [
- new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
- this.exchangeRateDataService,
- this.i18nService,
- Object.values(holdings),
- userSettings.language
- ),
- new CurrencyClusterRiskCurrentInvestment(
- this.exchangeRateDataService,
- this.i18nService,
- Object.values(holdings),
- userSettings.language
- )
- ],
- userSettings
- )
- : undefined
+ rules: hasOpenHoldings
+ ? await this.rulesService.evaluate(
+ [
+ new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
+ this.exchangeRateDataService,
+ this.i18nService,
+ Object.values(holdings),
+ userSettings.language
+ ),
+ new CurrencyClusterRiskCurrentInvestment(
+ this.exchangeRateDataService,
+ this.i18nService,
+ Object.values(holdings),
+ userSettings.language
+ )
+ ],
+ userSettings
+ )
+ : undefined
},
{
key: 'assetClassClusterRisk',
@@ -1212,26 +1213,25 @@ export class PortfolioService {
id: 'rule.assetClassClusterRisk.category',
languageCode: userSettings.language
}),
- rules:
- summary.activityCount > 0
- ? await this.rulesService.evaluate(
- [
- new AssetClassClusterRiskEquity(
- this.exchangeRateDataService,
- this.i18nService,
- userSettings.language,
- Object.values(holdings)
- ),
- new AssetClassClusterRiskFixedIncome(
- this.exchangeRateDataService,
- this.i18nService,
- userSettings.language,
- Object.values(holdings)
- )
- ],
- userSettings
- )
- : undefined
+ rules: hasOpenHoldings
+ ? await this.rulesService.evaluate(
+ [
+ new AssetClassClusterRiskEquity(
+ this.exchangeRateDataService,
+ this.i18nService,
+ userSettings.language,
+ Object.values(holdings)
+ ),
+ new AssetClassClusterRiskFixedIncome(
+ this.exchangeRateDataService,
+ this.i18nService,
+ userSettings.language,
+ Object.values(holdings)
+ )
+ ],
+ userSettings
+ )
+ : undefined
},
{
key: 'accountClusterRisk',
@@ -1266,28 +1266,27 @@ export class PortfolioService {
id: 'rule.economicMarketClusterRisk.category',
languageCode: userSettings.language
}),
- rules:
- summary.activityCount > 0
- ? await this.rulesService.evaluate(
- [
- new EconomicMarketClusterRiskDevelopedMarkets(
- this.exchangeRateDataService,
- this.i18nService,
- marketsTotalInBaseCurrency,
- markets.developedMarkets.valueInBaseCurrency,
- userSettings.language
- ),
- new EconomicMarketClusterRiskEmergingMarkets(
- this.exchangeRateDataService,
- this.i18nService,
- marketsTotalInBaseCurrency,
- markets.emergingMarkets.valueInBaseCurrency,
- userSettings.language
- )
- ],
- userSettings
- )
- : undefined
+ rules: hasOpenHoldings
+ ? await this.rulesService.evaluate(
+ [
+ new EconomicMarketClusterRiskDevelopedMarkets(
+ this.exchangeRateDataService,
+ this.i18nService,
+ marketsTotalInBaseCurrency,
+ markets.developedMarkets.valueInBaseCurrency,
+ userSettings.language
+ ),
+ new EconomicMarketClusterRiskEmergingMarkets(
+ this.exchangeRateDataService,
+ this.i18nService,
+ marketsTotalInBaseCurrency,
+ markets.emergingMarkets.valueInBaseCurrency,
+ userSettings.language
+ )
+ ],
+ userSettings
+ )
+ : undefined
},
{
key: 'regionalMarketClusterRisk',
@@ -1295,49 +1294,48 @@ export class PortfolioService {
id: 'rule.regionalMarketClusterRisk.category',
languageCode: userSettings.language
}),
- rules:
- summary.activityCount > 0
- ? await this.rulesService.evaluate(
- [
- new RegionalMarketClusterRiskAsiaPacific(
- this.exchangeRateDataService,
- this.i18nService,
- userSettings.language,
- marketsAdvancedTotalInBaseCurrency,
- marketsAdvanced.asiaPacific.valueInBaseCurrency
- ),
- new RegionalMarketClusterRiskEmergingMarkets(
- this.exchangeRateDataService,
- this.i18nService,
- userSettings.language,
- marketsAdvancedTotalInBaseCurrency,
- marketsAdvanced.emergingMarkets.valueInBaseCurrency
- ),
- new RegionalMarketClusterRiskEurope(
- this.exchangeRateDataService,
- this.i18nService,
- userSettings.language,
- marketsAdvancedTotalInBaseCurrency,
- marketsAdvanced.europe.valueInBaseCurrency
- ),
- new RegionalMarketClusterRiskJapan(
- this.exchangeRateDataService,
- this.i18nService,
- userSettings.language,
- marketsAdvancedTotalInBaseCurrency,
- marketsAdvanced.japan.valueInBaseCurrency
- ),
- new RegionalMarketClusterRiskNorthAmerica(
- this.exchangeRateDataService,
- this.i18nService,
- userSettings.language,
- marketsAdvancedTotalInBaseCurrency,
- marketsAdvanced.northAmerica.valueInBaseCurrency
- )
- ],
- userSettings
- )
- : undefined
+ rules: hasOpenHoldings
+ ? await this.rulesService.evaluate(
+ [
+ new RegionalMarketClusterRiskAsiaPacific(
+ this.exchangeRateDataService,
+ this.i18nService,
+ userSettings.language,
+ marketsAdvancedTotalInBaseCurrency,
+ marketsAdvanced.asiaPacific.valueInBaseCurrency
+ ),
+ new RegionalMarketClusterRiskEmergingMarkets(
+ this.exchangeRateDataService,
+ this.i18nService,
+ userSettings.language,
+ marketsAdvancedTotalInBaseCurrency,
+ marketsAdvanced.emergingMarkets.valueInBaseCurrency
+ ),
+ new RegionalMarketClusterRiskEurope(
+ this.exchangeRateDataService,
+ this.i18nService,
+ userSettings.language,
+ marketsAdvancedTotalInBaseCurrency,
+ marketsAdvanced.europe.valueInBaseCurrency
+ ),
+ new RegionalMarketClusterRiskJapan(
+ this.exchangeRateDataService,
+ this.i18nService,
+ userSettings.language,
+ marketsAdvancedTotalInBaseCurrency,
+ marketsAdvanced.japan.valueInBaseCurrency
+ ),
+ new RegionalMarketClusterRiskNorthAmerica(
+ this.exchangeRateDataService,
+ this.i18nService,
+ userSettings.language,
+ marketsAdvancedTotalInBaseCurrency,
+ marketsAdvanced.northAmerica.valueInBaseCurrency
+ )
+ ],
+ userSettings
+ )
+ : undefined
},
{
key: 'fees',
From bf923f1afe27705c2f758a68445ea81d127a4be5 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Fri, 31 Jul 2026 19:22:56 +0200
Subject: [PATCH 6/7] Task/improve style of tabs in various dialogs on mobile
(#7481)
* Improve tabs style
* Update changelog
---
CHANGELOG.md | 3 +++
.../account-detail-dialog/account-detail-dialog.html | 1 +
.../asset-profile-dialog/asset-profile-dialog.html | 1 +
.../holding-detail-dialog/holding-detail-dialog.html | 1 +
apps/client/src/styles.scss | 11 +++++++++++
5 files changed, 17 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d14f77f5e7..62cbee836d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
+- Improved the style of the tabs in the account detail dialog on mobile
+- Improved the style of the tabs in the holding detail dialog on mobile
+- Improved the style of the tabs in the asset profile dialog of the admin control panel on mobile
- Improved the style of the empty state in the _Fear & Greed Index_ component
- Improved the style of the type filter in the activities table component (experimental)
- Improved the performance of the property service by caching the properties in memory
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 32a11717f8..ff39f23b3e 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
@@ -33,6 +33,7 @@
diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html
index c9abdeeb7c..f0b914b1cc 100644
--- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html
+++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html
@@ -105,6 +105,7 @@
diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html
index 464f0b6b2b..c4f53497f3 100644
--- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html
+++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html
@@ -39,6 +39,7 @@
diff --git a/apps/client/src/styles.scss b/apps/client/src/styles.scss
index 6ef55cb322..739af30ae0 100644
--- a/apps/client/src/styles.scss
+++ b/apps/client/src/styles.scss
@@ -411,6 +411,17 @@ ngx-skeleton-loader {
.mdc-dialog__content {
--mat-dialog-supporting-text-color: rgba(var(--dark-primary-text));
}
+
+ @media (max-width: 575.98px) {
+ // Tabs fill the available width on mobile
+ .mat-mdc-tab-group {
+ .mat-mdc-tab {
+ flex-grow: 1;
+ min-width: unset;
+ padding: 0 0.5rem;
+ }
+ }
+ }
}
.mat-mdc-fab,
From eb62190c74b47b731702add1eae1abba7983b5dc Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Fri, 31 Jul 2026 19:32:28 +0200
Subject: [PATCH 7/7] Task/add activity count to delete menu item and
confirmation dialog of activities table component (#7489)
* Add activity count to delete menu and confirmation dialog
* Update changelog
---
CHANGELOG.md | 2 ++
.../activities-table.component.html | 13 +++++++++----
.../activities-table/activities-table.component.ts | 12 +++++++++++-
3 files changed, 22 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 62cbee836d..421a1bd6c3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improved the style of the tabs in the holding detail dialog on mobile
- Improved the style of the tabs in the asset profile dialog of the admin control panel on mobile
- Improved the style of the empty state in the _Fear & Greed Index_ component
+- Added the activity count to the delete menu item of the activities table
+- Added the activity count to the deletion confirmation dialog of the activities table
- Improved the style of the type filter in the activities table component (experimental)
- Improved the performance of the property service by caching the properties in memory
- Improved the language localization for German (`de`)
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 3d91852a0c..1ff8496a9b 100644
--- a/libs/ui/src/lib/activities-table/activities-table.component.html
+++ b/libs/ui/src/lib/activities-table/activities-table.component.html
@@ -84,14 +84,19 @@
diff --git a/libs/ui/src/lib/activities-table/activities-table.component.ts b/libs/ui/src/lib/activities-table/activities-table.component.ts
index 573c8c93a4..a4e7403a7b 100644
--- a/libs/ui/src/lib/activities-table/activities-table.component.ts
+++ b/libs/ui/src/lib/activities-table/activities-table.component.ts
@@ -282,6 +282,13 @@ export class GfActivitiesTableComponent implements AfterViewInit, OnInit {
);
}
+ public canDeleteActivities() {
+ return (
+ (this.dataSource()?.data.length ?? 0) > 0 &&
+ this.hasPermissionToDeleteActivity
+ );
+ }
+
public isExcludedFromAnalysis(activity: Activity) {
return (
(activity.account && isAccountExcluded(activity.account)) ??
@@ -314,7 +321,10 @@ export class GfActivitiesTableComponent implements AfterViewInit, OnInit {
this.activitiesDeleted.emit();
},
confirmType: ConfirmationDialogType.Warn,
- title: $localize`Do you really want to delete these activities?`
+ title:
+ this.totalItems === 1
+ ? $localize`Do you really want to delete this activity?`
+ : $localize`Do you really want to delete these ${this.totalItems}:count: activities?`
});
}