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.

This commit is contained in:
padreug 2025-06-22 16:27:37 +02:00
parent 234daebef7
commit 955eddd817
2 changed files with 146 additions and 16 deletions

View file

@ -270,6 +270,69 @@ window.app = Vue.createApp({
const costBasisData = this.analyticsData.cost_basis_history || [] const costBasisData = this.analyticsData.cost_basis_history || []
if (costBasisData.length === 0) { if (costBasisData.length === 0) {
console.log('No chart data available') 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 return
} }
@ -293,7 +356,17 @@ window.app = Vue.createApp({
) )
const labels = chartData.map(point => { 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', { return date.toLocaleDateString('en-US', {
month: 'short', month: 'short',
day: 'numeric' day: 'numeric'
@ -328,7 +401,13 @@ window.app = Vue.createApp({
createChart(labels, cumulativeSats) { createChart(labels, cumulativeSats) {
const ctx = this.$refs.dcaChart.getContext('2d') 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, { this.dcaChart = new Chart(ctx, {
type: 'line', type: 'line',
data: { data: {
@ -336,15 +415,19 @@ window.app = Vue.createApp({
datasets: [{ datasets: [{
label: 'Total Sats Accumulated', label: 'Total Sats Accumulated',
data: cumulativeSats, data: cumulativeSats,
borderColor: '#FF9500', // Bitcoin orange borderColor: '#FF9500',
backgroundColor: 'rgba(255, 149, 0, 0.1)', backgroundColor: gradient,
borderWidth: 3, borderWidth: 3,
fill: true, fill: true,
tension: 0.4, tension: 0.4,
pointBackgroundColor: '#FF9500', pointBackgroundColor: '#FFFFFF',
pointBorderColor: '#FF9500', pointBorderColor: '#FF9500',
pointRadius: 4, pointBorderWidth: 3,
pointHoverRadius: 6 pointRadius: 6,
pointHoverRadius: 8,
pointHoverBackgroundColor: '#FFFFFF',
pointHoverBorderColor: '#FF7700',
pointHoverBorderWidth: 4
}] }]
}, },
options: { options: {
@ -352,14 +435,24 @@ window.app = Vue.createApp({
maintainAspectRatio: false, maintainAspectRatio: false,
plugins: { plugins: {
legend: { legend: {
display: false // Hide legend to keep it clean display: false
}, },
tooltip: { tooltip: {
mode: 'index', mode: 'index',
intersect: false, intersect: false,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
titleColor: '#FFFFFF',
bodyColor: '#FFFFFF',
borderColor: '#FF9500',
borderWidth: 2,
cornerRadius: 8,
displayColors: false,
callbacks: { callbacks: {
title: function(context) {
return `📅 ${context[0].label}`
},
label: function (context) { 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, display: true,
grid: { grid: {
display: false display: false
},
ticks: {
color: '#666666',
font: {
size: 12,
weight: '500'
}
} }
}, },
y: { y: {
display: true, display: true,
beginAtZero: true,
grid: { grid: {
color: 'rgba(0,0,0,0.1)' color: 'rgba(255, 149, 0, 0.1)',
drawBorder: false
}, },
ticks: { ticks: {
color: '#666666',
font: {
size: 12,
weight: '500'
},
callback: function (value) { 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' return value.toLocaleString() + ' sats'
} }
} }
@ -387,6 +499,11 @@ window.app = Vue.createApp({
mode: 'nearest', mode: 'nearest',
axis: 'x', axis: 'x',
intersect: false intersect: false
},
elements: {
point: {
hoverRadius: 8
}
} }
} }
}) })

View file

@ -70,13 +70,26 @@ async def api_get_client_analytics(
time_range: str = Query("30d", regex="^(7d|30d|90d|1y|all)$"), time_range: str = Query("30d", regex="^(7d|30d|90d|1y|all)$"),
) -> ClientAnalytics: ) -> ClientAnalytics:
"""Get client performance analytics and cost basis data""" """Get client performance analytics and cost basis data"""
analytics = await get_client_analytics(wallet.wallet.user, time_range) try:
if not analytics: analytics = await get_client_analytics(wallet.wallet.user, time_range)
raise HTTPException( if not analytics:
status_code=HTTPStatus.NOT_FOUND, # Return empty analytics data instead of error
detail="Analytics data not available" 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") @satmachineclient_api_router.put("/api/v1/dashboard/settings")