Enhance chart loading and validation logic: Introduce loading state management to prevent multiple simultaneous requests, improve data validation for cumulative sats and labels, and add detailed logging for better debugging. Update HTML to ensure chart canvas is always rendered and disable button during loading.

This commit is contained in:
padreug 2025-06-22 18:01:10 +02:00
parent d50033f834
commit 6da61d953b
2 changed files with 143 additions and 41 deletions

View file

@ -54,7 +54,8 @@ window.app = Vue.createApp({
}, },
chartTimeRange: '30d', chartTimeRange: '30d',
dcaChart: null, dcaChart: null,
analyticsData: null analyticsData: null,
chartLoading: false
} }
}, },
@ -207,8 +208,12 @@ window.app = Vue.createApp({
return Math.min(Math.max(progress, 0), 100) return Math.min(Math.max(progress, 0), 100)
}, },
async loadChartData() { async loadChartData() {
// Prevent multiple simultaneous requests
if (this.chartLoading) return
try { try {
const { data } = await LNbits.api.request( this.chartLoading = true
const {data} = await LNbits.api.request(
'GET', 'GET',
`/satmachineclient/api/v1/dashboard/analytics?time_range=${this.chartTimeRange}`, `/satmachineclient/api/v1/dashboard/analytics?time_range=${this.chartTimeRange}`,
this.g.user.wallets[0].inkey this.g.user.wallets[0].inkey
@ -221,12 +226,20 @@ window.app = Vue.createApp({
} }
this.analyticsData = data this.analyticsData = data
// Use nextTick to ensure DOM is ready // Use nextTick to ensure DOM is ready, with retry logic
this.$nextTick(() => { this.$nextTick(() => {
this.initDCAChart() this.initDCAChart()
// If chart ref still not available, try again shortly
if (!this.$refs.dcaChart) {
setTimeout(() => {
this.initDCAChart()
}, 100)
}
}) })
} catch (error) { } catch (error) {
console.error('Error loading chart data:', error) console.error('Error loading chart data:', error)
} finally {
this.chartLoading = false
} }
}, },
@ -241,7 +254,13 @@ window.app = Vue.createApp({
} }
if (!this.$refs.dcaChart) { if (!this.$refs.dcaChart) {
console.log('No chart ref available') console.log('No chart ref available, waiting for DOM...')
// Try again after DOM update
this.$nextTick(() => {
if (this.$refs.dcaChart) {
this.initDCAChart()
}
})
return return
} }
@ -250,10 +269,14 @@ window.app = Vue.createApp({
console.error('Chart.js is not loaded') console.error('Chart.js is not loaded')
return return
} }
console.log('Chart.js version:', Chart.version || 'unknown')
console.log('Chart.js available:', typeof Chart)
// Destroy existing chart // Destroy existing chart
if (this.dcaChart) { if (this.dcaChart) {
this.dcaChart.destroy() this.dcaChart.destroy()
this.dcaChart = null
} }
const ctx = this.$refs.dcaChart.getContext('2d') const ctx = this.$refs.dcaChart.getContext('2d')
@ -270,7 +293,10 @@ window.app = Vue.createApp({
const cumulativeSats = [] const cumulativeSats = []
timelineData.forEach(point => { timelineData.forEach(point => {
runningSats += point.sats // Ensure sats is a valid number
const sats = point.sats || 0
const validSats = typeof sats === 'number' ? sats : parseFloat(sats) || 0
runningSats += validSats
const date = new Date(point.date) const date = new Date(point.date)
if (!isNaN(date.getTime())) { if (!isNaN(date.getTime())) {
@ -282,6 +308,8 @@ window.app = Vue.createApp({
} }
}) })
console.log('Timeline chart data:', { labels, cumulativeSats })
this.createChart(labels, cumulativeSats) this.createChart(labels, cumulativeSats)
return return
} }
@ -413,21 +441,61 @@ window.app = Vue.createApp({
day: 'numeric' day: 'numeric'
}) })
}) })
const cumulativeSats = uniqueChartData.map(point => point.cumulative_sats) const cumulativeSats = uniqueChartData.map(point => {
// Ensure cumulative_sats is a valid number
const sats = point.cumulative_sats || 0
return typeof sats === 'number' ? sats : parseFloat(sats) || 0
})
console.log('Final chart data:', { labels, cumulativeSats })
console.log('Labels array:', labels)
console.log('CumulativeSats array:', cumulativeSats)
// Validate data before creating chart
if (labels.length === 0 || cumulativeSats.length === 0) {
console.warn('No valid data for chart, skipping creation')
return
}
if (labels.length !== cumulativeSats.length) {
console.warn('Mismatched data arrays:', { labelsLength: labels.length, dataLength: cumulativeSats.length })
return
}
// Check for any invalid values in cumulativeSats
const hasInvalidValues = cumulativeSats.some(val => val === null || val === undefined || isNaN(val))
if (hasInvalidValues) {
console.warn('Invalid values found in cumulative sats:', cumulativeSats)
return
}
this.createChart(labels, cumulativeSats) this.createChart(labels, cumulativeSats)
}, },
createChart(labels, cumulativeSats) { createChart(labels, cumulativeSats) {
if (!this.$refs.dcaChart) {
console.log('Chart ref not available for createChart')
return
}
// Destroy existing chart
if (this.dcaChart) {
this.dcaChart.destroy()
this.dcaChart = null
}
const ctx = this.$refs.dcaChart.getContext('2d') const ctx = this.$refs.dcaChart.getContext('2d')
// Create gradient for the area fill try {
const gradient = ctx.createLinearGradient(0, 0, 0, 300) // Create gradient for the area fill
gradient.addColorStop(0, 'rgba(255, 149, 0, 0.4)') const gradient = ctx.createLinearGradient(0, 0, 0, 300)
gradient.addColorStop(0.5, 'rgba(255, 149, 0, 0.2)') gradient.addColorStop(0, 'rgba(255, 149, 0, 0.4)')
gradient.addColorStop(1, 'rgba(255, 149, 0, 0.05)') gradient.addColorStop(0.5, 'rgba(255, 149, 0, 0.2)')
gradient.addColorStop(1, 'rgba(255, 149, 0, 0.05)')
this.dcaChart = new Chart(ctx, {
// Small delay to ensure Chart.js is fully initialized
setTimeout(() => {
this.dcaChart = new Chart(ctx, {
type: 'line', type: 'line',
data: { data: {
labels: labels, labels: labels,
@ -526,6 +594,12 @@ window.app = Vue.createApp({
} }
} }
}) })
console.log('Chart created successfully in createChart!')
}, 50)
} catch (error) {
console.error('Error creating Chart.js chart in createChart:', error)
console.log('Chart data that failed:', { labels, cumulativeSats })
}
} }
}, },
@ -546,10 +620,18 @@ window.app = Vue.createApp({
}, },
mounted() { mounted() {
// Initialize chart after DOM is ready // Initialize chart after DOM is ready and data is loaded
this.$nextTick(() => { this.$nextTick(() => {
if (this.analyticsData) { console.log('Component mounted, checking for chart initialization')
console.log('Loading state:', this.loading)
console.log('Chart ref available:', !!this.$refs.dcaChart)
console.log('Analytics data available:', !!this.analyticsData)
if (this.analyticsData && this.$refs.dcaChart) {
console.log('Initializing chart from mounted hook')
this.initDCAChart() this.initDCAChart()
} else {
console.log('Chart will initialize after data loads')
} }
}) })
}, },
@ -558,5 +640,19 @@ window.app = Vue.createApp({
hasData() { hasData() {
return this.dashboardData && !this.loading return this.dashboardData && !this.loading
} }
},
watch: {
analyticsData: {
handler(newData) {
if (newData) {
console.log('Analytics data changed, initializing chart...')
this.$nextTick(() => {
this.initDCAChart()
})
}
},
immediate: false
}
} }
}) })

View file

@ -4,7 +4,7 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context {% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block scripts %} {{ window_vars(user) }} %} {% block scripts %} {{ window_vars(user) }}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
<script src="{{ static_url_for('satmachineclient/static', path='js/index.js') }}"></script> <script src="{{ static_url_for('satmachineclient/static', path='js/index.js') }}"></script>
{% endblock %} {% block page %} {% endblock %} {% block page %}
<div class="row q-col-gutter-md" id="dcaClient"> <div class="row q-col-gutter-md" id="dcaClient">
@ -214,32 +214,38 @@
</q-card-section> </q-card-section>
</q-card> </q-card>
<!-- DCA Performance Chart --> </div>
<q-card class="q-mb-md">
<q-card-section> <!-- DCA Performance Chart - Always render to ensure canvas is available -->
<h6 class="text-subtitle2 q-my-none q-mb-md">Bitcoin Accumulation Progress</h6> <q-card class="q-mb-md">
<div class="chart-container" style="position: relative; height: 300px;"> <q-card-section>
<canvas ref="dcaChart" style="max-height: 300px;"></canvas> <h6 class="text-subtitle2 q-my-none q-mb-md">Bitcoin Accumulation Progress</h6>
<div class="chart-container" style="position: relative; height: 300px;">
<canvas ref="dcaChart" style="max-height: 300px;"></canvas>
</div>
<div class="row q-mt-sm">
<div class="col">
<q-btn-toggle
v-model="chartTimeRange"
@update:model-value="loadChartData"
toggle-color="orange"
:options="[
{label: '7D', value: '7d'},
{label: '30D', value: '30d'},
{label: '90D', value: '90d'},
{label: 'ALL', value: 'all'}
]"
size="sm"
flat
:disable="chartLoading"
/>
</div> </div>
<div class="row q-mt-sm"> </div>
<div class="col"> </q-card-section>
<q-btn-toggle </q-card>
v-model="chartTimeRange"
@update:model-value="loadChartData" <!-- Dashboard Content -->
toggle-color="orange" <div v-if="hasData">
:options="[
{label: '7D', value: '7d'},
{label: '30D', value: '30d'},
{label: '90D', value: '90d'},
{label: 'ALL', value: 'all'}
]"
size="sm"
flat
/>
</div>
</div>
</q-card-section>
</q-card>
<!-- Transaction History --> <!-- Transaction History -->
<q-card> <q-card>