diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1024336b3..ad8f2f70e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,10 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added a close holding button to the holding detail dialog
+- Added the _Sponsors_ section to the about page
- Extended the user detail dialog in the users section of the admin control panel
### Changed
+- Refactored the generation of the holdings table in the _Copy AI prompt to clipboard for analysis_ action on the analysis page (experimental)
+- Refactored the generation of the holdings table in the _Copy portfolio data to clipboard for AI prompt_ action on the analysis page (experimental)
- Improved the language localization for German (`de`)
### Fixed
diff --git a/README.md b/README.md
index 82d5710d1..1a5cc6e95 100644
--- a/README.md
+++ b/README.md
@@ -297,7 +297,18 @@ Ghostfolio is **100% free** and **open source**. We encourage and support an act
Not sure what to work on? We have [some ideas](https://github.com/ghostfolio/ghostfolio/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22%20no%3Aassignee), even for [newcomers](https://github.com/ghostfolio/ghostfolio/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%20no%3Aassignee). Please join the Ghostfolio [Slack](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg) channel or post to [@ghostfolio\_](https://x.com/ghostfolio_) on _X_. We would love to hear from you.
-If you like to support this project, get [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) or [**Buy me a coffee**](https://www.buymeacoffee.com/ghostfolio).
+If you like to support this project, become a [**Sponsor**](https://github.com/sponsors/ghostfolio), get [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) or [**Buy me a coffee**](https://www.buymeacoffee.com/ghostfolio).
+
+## Sponsors
+
+
+
+ Browser testing via
+
+
+
+
+
## Analytics
diff --git a/apps/api/src/app/endpoints/ai/ai.service.ts b/apps/api/src/app/endpoints/ai/ai.service.ts
index 4cc4fde65..d07768d69 100644
--- a/apps/api/src/app/endpoints/ai/ai.service.ts
+++ b/apps/api/src/app/endpoints/ai/ai.service.ts
@@ -14,6 +14,27 @@ import type { ColumnDescriptor } from 'tablemark';
@Injectable()
export class AiService {
+ private static readonly HOLDINGS_TABLE_COLUMN_DEFINITIONS: ({
+ key:
+ | 'ALLOCATION_PERCENTAGE'
+ | 'ASSET_CLASS'
+ | 'ASSET_SUB_CLASS'
+ | 'CURRENCY'
+ | 'NAME'
+ | 'SYMBOL';
+ } & ColumnDescriptor)[] = [
+ { key: 'NAME', name: 'Name' },
+ { key: 'SYMBOL', name: 'Symbol' },
+ { key: 'CURRENCY', name: 'Currency' },
+ { key: 'ASSET_CLASS', name: 'Asset Class' },
+ { key: 'ASSET_SUB_CLASS', name: 'Asset Sub Class' },
+ {
+ align: 'right',
+ key: 'ALLOCATION_PERCENTAGE',
+ name: 'Allocation in Percentage'
+ }
+ ];
+
public constructor(
private readonly portfolioService: PortfolioService,
private readonly propertyService: PropertyService
@@ -59,14 +80,10 @@ export class AiService {
userId
});
- const holdingsTableColumns: ColumnDescriptor[] = [
- { name: 'Name' },
- { name: 'Symbol' },
- { name: 'Currency' },
- { name: 'Asset Class' },
- { name: 'Asset Sub Class' },
- { align: 'right', name: 'Allocation in Percentage' }
- ];
+ const holdingsTableColumns: ColumnDescriptor[] =
+ AiService.HOLDINGS_TABLE_COLUMN_DEFINITIONS.map(({ align, name }) => {
+ return { name, align: align ?? 'left' };
+ });
const holdingsTableRows = Object.values(holdings)
.sort((a, b) => {
@@ -78,17 +95,45 @@ export class AiService {
assetClass,
assetSubClass,
currency,
- name,
+ name: label,
symbol
}) => {
- return {
- Name: name,
- Symbol: symbol,
- Currency: currency,
- 'Asset Class': assetClass ?? '',
- 'Asset Sub Class': assetSubClass ?? '',
- 'Allocation in Percentage': `${(allocationInPercentage * 100).toFixed(3)}%`
- };
+ return AiService.HOLDINGS_TABLE_COLUMN_DEFINITIONS.reduce(
+ (row, { key, name }) => {
+ switch (key) {
+ case 'ALLOCATION_PERCENTAGE':
+ row[name] = `${(allocationInPercentage * 100).toFixed(3)}%`;
+ break;
+
+ case 'ASSET_CLASS':
+ row[name] = assetClass ?? '';
+ break;
+
+ case 'ASSET_SUB_CLASS':
+ row[name] = assetSubClass ?? '';
+ break;
+
+ case 'CURRENCY':
+ row[name] = currency;
+ break;
+
+ case 'NAME':
+ row[name] = label;
+ break;
+
+ case 'SYMBOL':
+ row[name] = symbol;
+ break;
+
+ default:
+ row[name] = '';
+ break;
+ }
+
+ return row;
+ },
+ {} as Record
+ );
}
);
diff --git a/apps/client/src/app/pages/about/overview/about-overview-page.html b/apps/client/src/app/pages/about/overview/about-overview-page.html
index 4085498a9..72c054170 100644
--- a/apps/client/src/app/pages/about/overview/about-overview-page.html
+++ b/apps/client/src/app/pages/about/overview/about-overview-page.html
@@ -175,7 +175,7 @@
-
+
}
+
+
+
+
Sponsors
+
+
Browser testing via
+
+
+
+
+
+
+
diff --git a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html
index b65054bba..fa74cd084 100644
--- a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html
+++ b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.html
@@ -5,7 +5,7 @@