From 955eddd817ed08dc0e65d8a382e73f7fa1be8017 Mon Sep 17 00:00:00 2001 From: padreug Date: Sun, 22 Jun 2025 16:27:37 +0200 Subject: [PATCH] Improve error handling and data validation in client analytics: Update API to return empty analytics data instead of raising an error when no data is available. Enhance date formatting checks in analytics calculations and frontend chart rendering to ensure robustness against invalid date formats. --- static/js/index.js | 137 +++++++++++++++++++++++++++++++++++++++++---- views_api.py | 25 +++++++-- 2 files changed, 146 insertions(+), 16 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index d8ba3a5..b015f1a 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -270,6 +270,69 @@ window.app = Vue.createApp({ const costBasisData = this.analyticsData.cost_basis_history || [] if (costBasisData.length === 0) { console.log('No chart data available') + // Create gradient for placeholder chart + const placeholderGradient = ctx.createLinearGradient(0, 0, 0, 300) + placeholderGradient.addColorStop(0, 'rgba(255, 149, 0, 0.3)') + placeholderGradient.addColorStop(1, 'rgba(255, 149, 0, 0.05)') + + // Show placeholder chart + this.dcaChart = new Chart(ctx, { + type: 'line', + data: { + labels: ['Start Your DCA Journey'], + datasets: [{ + label: 'Total Sats Accumulated', + data: [0], + borderColor: '#FF9500', + backgroundColor: placeholderGradient, + borderWidth: 3, + fill: true, + tension: 0.4, + pointRadius: 8, + pointBackgroundColor: '#FFFFFF', + pointBorderColor: '#FF9500', + pointBorderWidth: 3 + }] + }, + options: { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { display: false }, + tooltip: { + backgroundColor: 'rgba(0, 0, 0, 0.8)', + titleColor: '#FFFFFF', + bodyColor: '#FFFFFF', + borderColor: '#FF9500', + borderWidth: 2, + cornerRadius: 8 + } + }, + scales: { + x: { + grid: { display: false }, + ticks: { + color: '#666666', + font: { size: 12, weight: '500' } + } + }, + y: { + beginAtZero: true, + grid: { + color: 'rgba(255, 149, 0, 0.1)', + drawBorder: false + }, + ticks: { + color: '#666666', + font: { size: 12, weight: '500' }, + callback: function(value) { + return value.toLocaleString() + ' sats' + } + } + } + } + } + }) return } @@ -293,7 +356,17 @@ window.app = Vue.createApp({ ) const labels = chartData.map(point => { - const date = new Date(point.date) + // Handle different date formats with improved validation + let date; + if (point.date) { + date = new Date(point.date); + // Check if date is valid + if (isNaN(date.getTime())) { + date = new Date(); + } + } else { + date = new Date(); + } return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' @@ -328,7 +401,13 @@ window.app = Vue.createApp({ createChart(labels, cumulativeSats) { const ctx = this.$refs.dcaChart.getContext('2d') - + + // Create gradient for the area fill + const gradient = ctx.createLinearGradient(0, 0, 0, 300) + gradient.addColorStop(0, 'rgba(255, 149, 0, 0.4)') + gradient.addColorStop(0.5, 'rgba(255, 149, 0, 0.2)') + gradient.addColorStop(1, 'rgba(255, 149, 0, 0.05)') + this.dcaChart = new Chart(ctx, { type: 'line', data: { @@ -336,15 +415,19 @@ window.app = Vue.createApp({ datasets: [{ label: 'Total Sats Accumulated', data: cumulativeSats, - borderColor: '#FF9500', // Bitcoin orange - backgroundColor: 'rgba(255, 149, 0, 0.1)', + borderColor: '#FF9500', + backgroundColor: gradient, borderWidth: 3, fill: true, tension: 0.4, - pointBackgroundColor: '#FF9500', + pointBackgroundColor: '#FFFFFF', pointBorderColor: '#FF9500', - pointRadius: 4, - pointHoverRadius: 6 + pointBorderWidth: 3, + pointRadius: 6, + pointHoverRadius: 8, + pointHoverBackgroundColor: '#FFFFFF', + pointHoverBorderColor: '#FF7700', + pointHoverBorderWidth: 4 }] }, options: { @@ -352,14 +435,24 @@ window.app = Vue.createApp({ maintainAspectRatio: false, plugins: { legend: { - display: false // Hide legend to keep it clean + display: false }, tooltip: { mode: 'index', intersect: false, + backgroundColor: 'rgba(0, 0, 0, 0.8)', + titleColor: '#FFFFFF', + bodyColor: '#FFFFFF', + borderColor: '#FF9500', + borderWidth: 2, + cornerRadius: 8, + displayColors: false, callbacks: { + title: function(context) { + return `📅 ${context[0].label}` + }, label: function (context) { - return `${context.parsed.y.toLocaleString()} sats` + return `⚡ ${context.parsed.y.toLocaleString()} sats accumulated` } } } @@ -369,15 +462,34 @@ window.app = Vue.createApp({ display: true, grid: { display: false + }, + ticks: { + color: '#666666', + font: { + size: 12, + weight: '500' + } } }, y: { display: true, + beginAtZero: true, grid: { - color: 'rgba(0,0,0,0.1)' + color: 'rgba(255, 149, 0, 0.1)', + drawBorder: false }, ticks: { + color: '#666666', + font: { + size: 12, + weight: '500' + }, callback: function (value) { + if (value >= 1000000) { + return (value / 1000000).toFixed(1) + 'M sats' + } else if (value >= 1000) { + return (value / 1000).toFixed(0) + 'k sats' + } return value.toLocaleString() + ' sats' } } @@ -387,6 +499,11 @@ window.app = Vue.createApp({ mode: 'nearest', axis: 'x', intersect: false + }, + elements: { + point: { + hoverRadius: 8 + } } } }) diff --git a/views_api.py b/views_api.py index d1f5521..4b46774 100644 --- a/views_api.py +++ b/views_api.py @@ -70,13 +70,26 @@ async def api_get_client_analytics( time_range: str = Query("30d", regex="^(7d|30d|90d|1y|all)$"), ) -> ClientAnalytics: """Get client performance analytics and cost basis data""" - analytics = await get_client_analytics(wallet.wallet.user, time_range) - if not analytics: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, - detail="Analytics data not available" + try: + analytics = await get_client_analytics(wallet.wallet.user, time_range) + if not analytics: + # Return empty analytics data instead of error + return ClientAnalytics( + user_id=wallet.wallet.user, + cost_basis_history=[], + accumulation_timeline=[], + transaction_frequency={} + ) + return analytics + except Exception as e: + print(f"Analytics error: {e}") + # Return empty analytics data as fallback + return ClientAnalytics( + user_id=wallet.wallet.user, + cost_basis_history=[], + accumulation_timeline=[], + transaction_frequency={} ) - return analytics @satmachineclient_api_router.put("/api/v1/dashboard/settings")