From 8f4899cf8ba8737e695ba30682d70e05bd9e4275 Mon Sep 17 00:00:00 2001 From: kris Date: Fri, 19 Sep 2025 12:11:20 +0000 Subject: [PATCH] Add dashboard top 5 accounts display and form cancel/add functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Features added: - Dashboard now shows top 5 holdings, accounts, and subscriptions under each segment - Quick action cards positioned underneath data cards for easy access - Cancel/Add buttons added to all forms (trade, cash account, subscription) - Auto-focus functionality when navigating from quick actions - Responsive design optimized for mobile and desktop - Consistent styling following existing design patterns UI improvements: - Card groups structure for better organization - Top accounts sections with hover effects and proper formatting - Form actions container with proper button alignment - Mobile-responsive quick action cards and forms 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- index.html | 104 ++++++++++++++------ script.js | 172 +++++++++++++++++++++++++++++++++ styles.css | 274 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 510 insertions(+), 40 deletions(-) diff --git a/index.html b/index.html index 7b04716..69649ae 100644 --- a/index.html +++ b/index.html @@ -103,43 +103,81 @@
-
-
-

Portfolio

+
+
+
+

Portfolio

+
+
+
€0.00
+
€0.00
+
+
+
Top Holdings
+
+
No positions yet
+
+
-
-
€0.00
-
€0.00
+
+
âž•
+
Add Trade
-
-
-

Cash

+
+
+
+

Cash

+
+
+
€0.00
+
0 accounts
+
+
+
Top Accounts
+
+
No accounts yet
+
+
-
-
€0.00
-
0 accounts
+
+
💰
+
Add Account
-
-
-

Subscriptions

+
+
+
+

Subscriptions

+
+
+
€0.00/mo
+
0 services
+
+
+
Top Services
+
+
No subscriptions yet
+
+
-
-
€0.00/mo
-
0 services
+
+
📱
+
Add Service
-
-
-

Total Value

-
-
-
€0.00
-
0.0%
+
+
+
+

Total Value

+
+
+
€0.00
+
0.0%
+
@@ -203,8 +241,11 @@
- - + +
+ + +
@@ -427,7 +468,10 @@ curl -H "Authorization: Bearer YOUR_TOKEN" \
- +
+ + +
@@ -1044,8 +1088,8 @@ curl -H "Authorization: Bearer YOUR_TOKEN" \
- - + +
diff --git a/script.js b/script.js index 3ede27c..5214495 100644 --- a/script.js +++ b/script.js @@ -137,12 +137,34 @@ class ETFTradeTracker { this.renderTokensPage(); } else if (pageId === 'cash-accounts') { this.renderCashAccountsPage(); + // Auto-scroll to add account form if coming from quick action + if (document.referrer === '' || window.location.hash === '#add-account') { + setTimeout(() => { + const addAccountForm = document.getElementById('create-account-form'); + if (addAccountForm) { + addAccountForm.scrollIntoView({ behavior: 'smooth', block: 'start' }); + const firstInput = addAccountForm.querySelector('input[type="text"]'); + if (firstInput) firstInput.focus(); + } + }, 100); + } } else if (pageId === 'add-transfer') { this.renderAddTransferPage(); } else if (pageId === 'subscriptions') { this.renderSubscriptionsPage(); } else if (pageId === 'add-subscription') { this.renderAddSubscriptionPage(); + // Auto-focus first input when coming from quick action + setTimeout(() => { + const firstInput = document.getElementById('subscription-service-name'); + if (firstInput) firstInput.focus(); + }, 100); + } else if (pageId === 'add-trade') { + // Auto-focus first input when coming from quick action + setTimeout(() => { + const firstInput = document.getElementById('etf-symbol'); + if (firstInput) firstInput.focus(); + }, 100); } else if (pageId === 'admin') { this.renderAdminPage(); } @@ -768,6 +790,9 @@ class ETFTradeTracker { document.getElementById('dashboard-total-combined').textContent = this.formatCurrency(totalValue, 'EUR'); document.getElementById('dashboard-performance').textContent = `${portfolioData.gainsPercentage >= 0 ? '+' : ''}${portfolioData.gainsPercentage.toFixed(1)}%`; + + // Load and display top accounts for each segment + this.loadDashboardTopAccounts(); } updateDashboardColors(totalGains) { @@ -802,6 +827,153 @@ class ETFTradeTracker { } } + async loadDashboardTopAccounts() { + try { + // Load data for all segments in parallel + const [portfolioData, cashData, subscriptionData] = await Promise.all([ + this.getTopPortfolioHoldings(), + this.getTopCashAccounts(), + this.getTopSubscriptions() + ]); + + // Render each segment + this.renderTopPortfolio(portfolioData); + this.renderTopCash(cashData); + this.renderTopSubscriptions(subscriptionData); + } catch (error) { + console.error('Error loading dashboard top accounts:', error); + } + } + + getTopPortfolioHoldings() { + const etfMap = this.getActiveETFPositions(); + const etfArray = Array.from(etfMap.values()) + .filter(etf => etf.shares > 0) + .map(etf => { + const currentPrice = this.currentPrices.get(etf.symbol); + const currentValue = currentPrice ? etf.shares * currentPrice : etf.totalValue; + return { + name: etf.symbol, + value: currentValue, + details: `${etf.shares.toFixed(3)} shares`, + currency: etf.currency + }; + }) + .sort((a, b) => b.value - a.value) + .slice(0, 5); + + return etfArray; + } + + async getTopCashAccounts() { + try { + const response = await fetch(`${this.apiUrl}/cash-accounts`, { + credentials: 'include' + }); + + if (response.ok) { + const accounts = await response.json(); + return accounts + .sort((a, b) => b.balance - a.balance) + .slice(0, 5) + .map(account => ({ + name: account.name, + value: account.balance, + details: account.account_type, + currency: account.currency + })); + } + } catch (error) { + console.error('Error fetching top cash accounts:', error); + } + return []; + } + + async getTopSubscriptions() { + try { + const response = await fetch(`${this.apiUrl}/subscriptions`, { + credentials: 'include' + }); + + if (response.ok) { + const subscriptions = await response.json(); + return subscriptions + .sort((a, b) => { + const aCost = a.billingCycle === 'monthly' ? a.monthlyPrice : (a.annualPrice / 12); + const bCost = b.billingCycle === 'monthly' ? b.monthlyPrice : (b.annualPrice / 12); + return bCost - aCost; + }) + .slice(0, 5) + .map(sub => ({ + name: sub.serviceName, + value: sub.billingCycle === 'monthly' ? sub.monthlyPrice : (sub.annualPrice / 12), + details: sub.category, + currency: sub.currency + })); + } + } catch (error) { + console.error('Error fetching top subscriptions:', error); + } + return []; + } + + renderTopPortfolio(holdings) { + const container = document.getElementById('dashboard-top-portfolio'); + + if (!holdings || holdings.length === 0) { + container.innerHTML = '
No positions yet
'; + return; + } + + const html = holdings.map(holding => ` + + `).join(''); + + container.innerHTML = html; + } + + renderTopCash(accounts) { + const container = document.getElementById('dashboard-top-cash'); + + if (!accounts || accounts.length === 0) { + container.innerHTML = '
No accounts yet
'; + return; + } + + const html = accounts.map(account => ` + + `).join(''); + + container.innerHTML = html; + } + + renderTopSubscriptions(subscriptions) { + const container = document.getElementById('dashboard-top-subscriptions'); + + if (!subscriptions || subscriptions.length === 0) { + container.innerHTML = '
No subscriptions yet
'; + return; + } + + const html = subscriptions.map(sub => ` + + `).join(''); + + container.innerHTML = html; + } + calculateDashboardMetrics() { const now = new Date(); const currentMonth = now.getMonth(); diff --git a/styles.css b/styles.css index d57cef1..263de84 100644 --- a/styles.css +++ b/styles.css @@ -603,6 +603,13 @@ body { margin: 0 auto; } +/* Card Groups - Each group contains a data card and its quick action card */ +.card-group { + display: flex; + flex-direction: column; + gap: 10px; +} + @media (max-width: 768px) { .dashboard-summary { grid-template-columns: 1fr 1fr; @@ -625,19 +632,9 @@ body { box-shadow: var(--shadow-light); border-left: 4px solid var(--accent-primary); transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease; - cursor: pointer; user-select: none; } -.summary-card:hover { - transform: translateY(-3px); - box-shadow: var(--shadow-heavy); - background: var(--bg-tertiary); -} - -.summary-card:active { - transform: translateY(-1px); -} .summary-header { margin-bottom: 15px; @@ -702,6 +699,241 @@ body { } } +/* Top Accounts Section Styles */ +.top-accounts-section { + margin-top: 20px; + padding-top: 15px; + border-top: 1px solid var(--border-light); +} + +.top-accounts-title { + font-size: 0.85rem; + color: var(--text-secondary); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 10px; +} + +.top-accounts-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.top-account-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 0; + font-size: 0.85rem; + border-bottom: 1px solid var(--border-light); + transition: background-color 0.2s ease; +} + +.top-account-item:last-child { + border-bottom: none; +} + +.top-account-item:hover { + background-color: var(--bg-tertiary); + margin: 0 -10px; + padding-left: 10px; + padding-right: 10px; + border-radius: 4px; +} + +.top-account-name { + color: var(--text-primary); + font-weight: 500; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.top-account-details { + color: var(--text-muted); + font-size: 0.75rem; + margin-left: 8px; +} + +.top-account-value { + color: var(--text-secondary); + font-weight: 600; + text-align: right; + min-width: 60px; +} + +.no-data-small { + color: var(--text-muted); + font-size: 0.8rem; + text-align: center; + padding: 10px 0; + font-style: italic; +} + +/* Responsive adjustments for top accounts */ +@media (max-width: 768px) { + .top-accounts-section { + margin-top: 15px; + padding-top: 12px; + } + + .top-account-item { + font-size: 0.8rem; + padding: 6px 0; + } + + .top-account-details { + display: none; + } + + .top-account-value { + min-width: 50px; + font-size: 0.8rem; + } +} + +@media (max-width: 480px) { + .top-accounts-title { + font-size: 0.8rem; + margin-bottom: 8px; + } + + .top-account-name { + font-size: 0.75rem; + } + + .top-account-value { + font-size: 0.75rem; + min-width: 45px; + } +} + +/* Quick Action Cards */ +.quick-action-card { + background: var(--bg-secondary); + border-radius: 6px; + padding: 8px 12px; + border: 1px dashed var(--border-medium); + box-shadow: var(--shadow-light); + transition: all 0.2s ease; + cursor: pointer; + user-select: none; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + text-align: center; + position: relative; + overflow: hidden; + min-height: 35px; + gap: 6px; +} + +.quick-action-card:hover { + transform: translateY(-1px); + box-shadow: var(--shadow-light); + border-color: var(--accent-primary); + background: var(--bg-tertiary); +} + +.quick-action-card:active { + transform: translateY(0px); +} + +.quick-action-icon { + font-size: 0.9rem; + opacity: 0.8; + transition: all 0.2s ease; +} + +.quick-action-text { + font-size: 0.75rem; + font-weight: 600; + color: var(--text-secondary); + transition: color 0.2s ease; +} + +.quick-action-card:hover .quick-action-icon { + opacity: 1; +} + +.quick-action-card:hover .quick-action-text { + color: var(--text-primary); +} + +/* Specific action card styling */ +.portfolio-action { + border-color: var(--success); +} + +.portfolio-action:hover { + border-color: var(--success); +} + +.portfolio-action:hover .quick-action-text { + color: var(--success); +} + +.cash-action { + border-color: var(--info); +} + +.cash-action:hover { + border-color: var(--info); +} + +.cash-action:hover .quick-action-text { + color: var(--info); +} + +.subscription-action { + border-color: var(--accent-purple); +} + +.subscription-action:hover { + border-color: var(--accent-purple); +} + +.subscription-action:hover .quick-action-text { + color: var(--accent-purple); +} + +/* Responsive adjustments for quick action cards */ +@media (max-width: 768px) { + .quick-action-card { + padding: 6px 10px; + min-height: 30px; + gap: 5px; + } + + .quick-action-icon { + font-size: 0.8rem; + } + + .quick-action-text { + font-size: 0.7rem; + } +} + +@media (max-width: 480px) { + .quick-action-card { + padding: 5px 8px; + min-height: 28px; + gap: 4px; + } + + .quick-action-icon { + font-size: 0.75rem; + } + + .quick-action-text { + font-size: 0.65rem; + } +} + .dashboard-card.total-value { border-left-color: var(--success); } @@ -1439,6 +1671,28 @@ body { transform: translateY(0); } +/* Form Actions Container */ +.form-actions { + display: flex; + gap: 12px; + justify-content: flex-end; + align-items: center; + margin-top: 20px; + padding-top: 15px; + border-top: 1px solid var(--border-light); +} + +@media (max-width: 480px) { + .form-actions { + flex-direction: column-reverse; + gap: 10px; + } + + .form-actions button { + width: 100%; + } +} + .trades-controls { display: flex; gap: 15px;