From ae6b0ac80e2fdc55faded841eb4efafdb40182ed Mon Sep 17 00:00:00 2001 From: kris Date: Mon, 1 Sep 2025 17:37:41 +0000 Subject: [PATCH] Add GBP currency support and update comprehensive documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add British Pound (GBP) support to all database schemas and constraints - Update API validation to accept EUR, USD, and GBP currencies - Implement centralized currency symbol mapping with getCurrencySymbol() - Replace all hardcoded currency mappings throughout frontend code - Add GBP options to all currency dropdown menus in UI forms - Update README with enhanced feature descriptions and changelog - Document multi-currency support and historical price tracking features - Improve project documentation with comprehensive feature overview 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 21 +++++++++++++++++++-- index.html | 3 +++ script.js | 37 +++++++++++++++++++++++-------------- server.js | 12 ++++++------ 4 files changed, 51 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 535fba4..ea64a71 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ A web-based application for tracking ETF trades with multi-user authentication, - 💼 **Portfolio Management** - Track your ETF positions, shares, and investments - 📊 **Trade History** - Complete record of all buy/sell transactions - 📈 **Gains/Losses Analysis** - Calculate performance with current market prices +- 📉 **Historical Price Tracking** - Store and view price update history with timestamps +- 💰 **Multi-Currency Support** - Full support for EUR, USD, and GBP currencies +- 🏦 **Cash Account Management** - Track savings accounts and cash transfers +- 📊 **Capital Gains Tax (CGT)** - Automatic CGT calculations with long-term rates - 🎯 **Dashboard** - Overview of portfolio metrics and performance - 👥 **Admin Panel** - User management for administrators - 📱 **Responsive Design** - Works on desktop and mobile devices @@ -19,7 +23,7 @@ A web-based application for tracking ETF trades with multi-user authentication, Clean overview of your portfolio with key metrics and ETF breakdown. ### Trade Entry -Simple form to add new ETF trades with date/time and currency support (EUR/USD). +Simple form to add new ETF trades with date/time and currency support (EUR/USD/GBP). ### Gains/Losses Update current market prices to see real-time portfolio performance. @@ -200,7 +204,7 @@ When you first run the application, a default admin user is created: - `trade_type` - 'buy' or 'sell' - `shares` - Number of shares - `price` - Price per share -- `currency` - 'EUR' or 'USD' +- `currency` - 'EUR', 'USD', or 'GBP' - `trade_datetime` - Date and time of trade - `fees` - Optional trading fees - `notes` - Optional trade notes @@ -269,6 +273,19 @@ For issues or questions: ## Changelog +### v1.2.0 (Latest) +- **Historical Price Tracking**: Store and view price update history for all ETFs +- **Price History Modal**: View timeline of price updates with timestamps +- **Persistent Pricing**: Automatically load latest prices on app startup +- **Multi-Currency Support**: Added British Pound (GBP) alongside EUR/USD +- **Enhanced UX**: Improved price update notifications and database persistence + +### v1.1.0 +- Comprehensive cash savings and transfers system +- Capital Gains Tax (CGT) calculations with 8+ year long-term rates +- Total holdings tracking across ETFs and cash accounts +- Enhanced portfolio analytics and reporting + ### v1.0.0 (Initial Release) - Multi-user authentication system - ETF trade tracking diff --git a/index.html b/index.html index 108cc1d..a0e010f 100644 --- a/index.html +++ b/index.html @@ -222,6 +222,7 @@ + @@ -448,6 +449,7 @@ curl -H "Authorization: Bearer YOUR_TOKEN" \
@@ -509,6 +511,7 @@ curl -H "Authorization: Bearer YOUR_TOKEN" \
diff --git a/script.js b/script.js index 0964b71..7c2d5e7 100644 --- a/script.js +++ b/script.js @@ -482,7 +482,7 @@ class ETFTradeTracker { const dateTime = new Date(trade.dateTime); const formattedDate = dateTime.toLocaleDateString(); const formattedTime = dateTime.toTimeString().split(' ')[0]; - const currencySymbol = trade.currency === 'EUR' ? '€' : '$'; + const currencySymbol = this.getCurrencySymbol(trade.currency); return `
@@ -781,7 +781,7 @@ class ETFTradeTracker { } const breakdownHTML = etfEntries.map(etf => { - const currencySymbol = etf.currency === 'EUR' ? '€' : '$'; + const currencySymbol = this.getCurrencySymbol(etf.currency); const avgPrice = etf.shares > 0 ? etf.totalValue / etf.shares : 0; return ` @@ -936,7 +936,7 @@ class ETFTradeTracker { accountsList.innerHTML = accounts.slice(0, 5).map(account => { const balance = parseFloat(account.balance); - const currencySymbol = account.currency === 'EUR' ? '€' : '$'; + const currencySymbol = this.getCurrencySymbol(account.currency); const formattedBalance = `${currencySymbol}${balance.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2})}`; return ` @@ -1023,7 +1023,7 @@ class ETFTradeTracker {

Holdings

${activeETFs.map(etf => { - const currencySymbol = etf.currency === 'EUR' ? '€' : '$'; + const currencySymbol = this.getCurrencySymbol(etf.currency); const allocation = ((etf.totalValue / totalPortfolioValue) * 100).toFixed(1); return ` @@ -1085,7 +1085,7 @@ class ETFTradeTracker { const updateHTML = Array.from(etfMap.entries()).map(([symbol, etf]) => { const currentPrice = this.currentPrices.get(symbol) || ''; - const currencySymbol = etf.currency === 'EUR' ? '€' : '$'; + const currencySymbol = this.getCurrencySymbol(etf.currency); return `
@@ -1280,7 +1280,7 @@ class ETFTradeTracker { if (etf) { const changeAmount = currentPrice - etf.avgPrice; const changePercent = ((changeAmount / etf.avgPrice) * 100); - const currencySymbol = etf.currency === 'EUR' ? '€' : '$'; + const currencySymbol = this.getCurrencySymbol(etf.currency); const changeElement = document.querySelector(`[data-symbol="${symbol}"].price-change`); if (changeElement) { @@ -1379,7 +1379,7 @@ class ETFTradeTracker { const currentValue = etf.shares * currentPrice; const gainLoss = currentValue - etf.totalValue; const percentage = etf.totalValue > 0 ? ((gainLoss / etf.totalValue) * 100) : 0; - const currencySymbol = etf.currency === 'EUR' ? '€' : '$'; + const currencySymbol = this.getCurrencySymbol(etf.currency); const performanceClass = gainLoss >= 0 ? 'positive' : 'negative'; const hasRealPrice = this.currentPrices.has(symbol); @@ -1611,7 +1611,7 @@ class ETFTradeTracker { } const breakdownHTML = eligiblePositions.map(position => { - const currencySymbol = position.currency === 'EUR' ? '€' : '$'; + const currencySymbol = this.getCurrencySymbol(position.currency); const gainsClass = position.gains >= 0 ? 'positive' : 'negative'; const performanceClass = position.gains >= 0 ? 'positive' : 'negative'; const gainsPercentage = position.cost > 0 ? ((position.gains / position.cost) * 100) : 0; @@ -1993,7 +1993,7 @@ class ETFTradeTracker { accountsList.innerHTML = accounts.map(account => { const createdDate = new Date(account.created_at).toLocaleDateString(); const balance = parseFloat(account.balance); - const currencySymbol = account.currency === 'EUR' ? '€' : '$'; + const currencySymbol = this.getCurrencySymbol(account.currency); const formattedBalance = `${currencySymbol}${balance.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2})}`; const accountTypeMap = { @@ -2267,7 +2267,7 @@ class ETFTradeTracker { transfersList.innerHTML = transfers.map(transfer => { const transferDate = new Date(transfer.transfer_date).toLocaleDateString(); const amount = parseFloat(transfer.amount); - const currencySymbol = transfer.currency === 'USD' ? '$' : '€'; + const currencySymbol = this.getCurrencySymbol(transfer.currency); return `
@@ -2305,7 +2305,7 @@ class ETFTradeTracker { // Add active accounts const activeAccounts = accounts.filter(account => account.is_active); activeAccounts.forEach(account => { - const currencySymbol = account.currency === 'USD' ? '$' : '€'; + const currencySymbol = this.getCurrencySymbol(account.currency); const option = document.createElement('option'); option.value = account.id; option.textContent = `${account.account_name} (${currencySymbol}${parseFloat(account.balance).toFixed(2)})`; @@ -2422,7 +2422,7 @@ class ETFTradeTracker { // Add active accounts const activeAccounts = accounts.filter(account => account.is_active); activeAccounts.forEach(account => { - const currencySymbol = account.currency === 'USD' ? '$' : '€'; + const currencySymbol = this.getCurrencySymbol(account.currency); const option = document.createElement('option'); option.value = account.id; option.textContent = `${account.account_name} (${currencySymbol}${parseFloat(account.balance).toFixed(2)})`; @@ -2466,7 +2466,7 @@ class ETFTradeTracker { transfersList.innerHTML = recentTransfers.map(transfer => { const transferDate = new Date(transfer.transfer_date).toLocaleDateString(); const amount = parseFloat(transfer.amount); - const currencySymbol = transfer.currency === 'USD' ? '$' : '€'; + const currencySymbol = this.getCurrencySymbol(transfer.currency); return `
@@ -2804,7 +2804,7 @@ class ETFTradeTracker { existingModal.remove(); } - const currencySymbol = history[0]?.currency === 'EUR' ? '€' : '$'; + const currencySymbol = this.getCurrencySymbol(history[0]?.currency); const modal = document.createElement('div'); modal.id = 'price-history-modal'; @@ -2856,6 +2856,15 @@ class ETFTradeTracker { } } + getCurrencySymbol(currency) { + switch(currency) { + case 'EUR': return '€'; + case 'USD': return '$'; + case 'GBP': return '£'; + default: return currency; + } + } + escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; diff --git a/server.js b/server.js index 7f41229..d24053f 100644 --- a/server.js +++ b/server.js @@ -65,7 +65,7 @@ function initializeDatabase() { trade_type TEXT NOT NULL CHECK (trade_type IN ('buy', 'sell')), shares REAL NOT NULL CHECK (shares > 0), price REAL NOT NULL CHECK (price > 0), - currency TEXT NOT NULL CHECK (currency IN ('EUR', 'USD')), + currency TEXT NOT NULL CHECK (currency IN ('EUR', 'USD', 'GBP')), trade_datetime TEXT NOT NULL, fees REAL DEFAULT 0 CHECK (fees >= 0), notes TEXT, @@ -180,7 +180,7 @@ function initializeDatabase() { account_name TEXT NOT NULL, account_type TEXT DEFAULT 'savings' CHECK (account_type IN ('savings', 'checking', 'money_market', 'cd', 'other')), balance REAL NOT NULL DEFAULT 0 CHECK (balance >= 0), - currency TEXT NOT NULL DEFAULT 'EUR' CHECK (currency IN ('EUR', 'USD')), + currency TEXT NOT NULL DEFAULT 'EUR' CHECK (currency IN ('EUR', 'USD', 'GBP')), institution_name TEXT, interest_rate REAL DEFAULT 0 CHECK (interest_rate >= 0), notes TEXT, @@ -230,7 +230,7 @@ function initializeDatabase() { user_id INTEGER NOT NULL, etf_symbol TEXT NOT NULL, price REAL NOT NULL CHECK (price > 0), - currency TEXT NOT NULL CHECK (currency IN ('EUR', 'USD')), + currency TEXT NOT NULL CHECK (currency IN ('EUR', 'USD', 'GBP')), updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ) @@ -990,7 +990,7 @@ app.post('/api/trades', requireAuthOrToken, (req, res) => { return res.status(400).json({ error: 'Invalid trade type' }); } - if (!['EUR', 'USD'].includes(currency)) { + if (!['EUR', 'USD', 'GBP'].includes(currency)) { return res.status(400).json({ error: 'Invalid currency' }); } @@ -1232,8 +1232,8 @@ app.post('/api/price-history', requireAuthOrToken, (req, res) => { return res.status(400).json({ error: 'Price must be greater than 0' }); } - if (!['EUR', 'USD'].includes(currency)) { - return res.status(400).json({ error: 'Currency must be EUR or USD' }); + if (!['EUR', 'USD', 'GBP'].includes(currency)) { + return res.status(400).json({ error: 'Currency must be EUR, USD, or GBP' }); } const sql = `