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:
parent
234daebef7
commit
955eddd817
2 changed files with 146 additions and 16 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
25
views_api.py
25
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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue