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")