diff --git a/static/js/index.js b/static/js/index.js index c6e6209..42ee57c 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -1,5 +1,5 @@ window.app = Vue.createApp({ - el: '#dcaClient', + el: '#vue', mixins: [windowMixin], delimiters: ['${', '}'], data: function () { @@ -14,7 +14,7 @@ window.app = Vue.createApp({ fixed_mode_daily_limit: null, username: '' }, - + // Dashboard state dashboardData: null, transactions: [], @@ -80,17 +80,17 @@ window.app = Vue.createApp({ '/satmachineclient/api/v1/registration-status', this.g.user.wallets[0].adminkey ) - + this.isRegistered = data.is_registered this.registrationChecked = true - + if (!this.isRegistered) { this.showRegistrationDialog = true // Pre-fill username and default wallet if available this.registrationForm.username = this.g.user.username || '' - this.registrationForm.selectedWallet = this.g.user.wallets[0] || null + this.registrationForm.selectedWallet = this.g.user.wallets[0]?.id || null } - + return data } catch (error) { console.error('Error checking registration status:', error) @@ -108,26 +108,32 @@ window.app = Vue.createApp({ username: this.registrationForm.username || this.g.user.username || `user_${this.g.user.id.substring(0, 8)}` } + // Find the selected wallet object to get the adminkey + const selectedWallet = this.g.user.wallets.find(w => w.id === this.registrationForm.selectedWallet) + if (!selectedWallet) { + throw new Error('Selected wallet not found') + } + const { data } = await LNbits.api.request( 'POST', '/satmachineclient/api/v1/register', - this.registrationForm.selectedWallet.adminkey, + selectedWallet.adminkey, registrationData ) - + this.isRegistered = true this.showRegistrationDialog = false - + this.$q.notify({ type: 'positive', message: data.message || 'Successfully registered for DCA!', icon: 'check_circle', position: 'top' }) - + // Load dashboard data after successful registration await this.loadDashboardData() - + } catch (error) { console.error('Error registering client:', error) this.$q.notify({ @@ -297,18 +303,18 @@ window.app = Vue.createApp({ console.log('Chart already loading, ignoring request') return } - + try { this.chartLoading = true - + // Destroy existing chart immediately to prevent conflicts if (this.dcaChart) { console.log('Destroying existing chart before loading new data') this.dcaChart.destroy() this.dcaChart = null } - - const {data} = await LNbits.api.request( + + const { data } = await LNbits.api.request( 'GET', `/satmachineclient/api/v1/dashboard/analytics?time_range=${this.chartTimeRange}`, this.g.user.wallets[0].adminkey @@ -321,10 +327,10 @@ window.app = Vue.createApp({ } this.analyticsData = data - + // Wait for DOM update and ensure we're still in loading state await this.$nextTick() - + // Double-check we're still the active loading request if (this.chartLoading) { this.initDCAChart() @@ -343,13 +349,13 @@ window.app = Vue.createApp({ console.log('analyticsData:', this.analyticsData) console.log('dcaChart ref:', this.$refs.dcaChart) console.log('chartLoading state:', this.chartLoading) - + // Skip if we're not in a loading state (indicates this is a stale call) if (!this.chartLoading && this.dcaChart) { console.log('Chart already exists and not loading, skipping initialization') return } - + if (!this.analyticsData) { console.log('No analytics data available') return @@ -371,7 +377,7 @@ window.app = Vue.createApp({ console.error('Chart.js is not loaded') return } - + console.log('Chart.js version:', Chart.version || 'unknown') console.log('Chart.js available:', typeof Chart) @@ -383,45 +389,45 @@ window.app = Vue.createApp({ } const ctx = this.$refs.dcaChart.getContext('2d') - + // Use accumulation_timeline data which is already grouped by day const timelineData = this.analyticsData.accumulation_timeline || [] console.log('Timeline data sample:', timelineData.slice(0, 2)) // Debug first 2 records - + // If we have timeline data, use it (already grouped by day) if (timelineData.length > 0) { // Calculate running totals from daily data let runningSats = 0 const labels = [] const cumulativeSats = [] - + timelineData.forEach(point => { // 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) if (!isNaN(date.getTime())) { - labels.push(date.toLocaleDateString('en-US', { - month: 'short', - day: 'numeric' + labels.push(date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric' })) cumulativeSats.push(runningSats) } }) - + console.log('Timeline chart data:', { labels, cumulativeSats }) - + this.createChart(labels, cumulativeSats) return } - + // Fallback to cost_basis_history but group by date to avoid duplicates console.log('No timeline data, using cost_basis_history as fallback') const chartData = this.analyticsData.cost_basis_history || [] console.log('Chart data sample:', chartData.slice(0, 2)) // Debug first 2 records - + // Handle empty data case if (chartData.length === 0) { console.log('No chart data available') @@ -429,7 +435,7 @@ window.app = Vue.createApp({ 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 with enhanced styling this.dcaChart = new Chart(ctx, { type: 'line', @@ -463,7 +469,7 @@ window.app = Vue.createApp({ borderWidth: 2, cornerRadius: 8, callbacks: { - label: function(context) { + label: function (context) { return `${context.parsed.y.toLocaleString()} sats` } } @@ -486,7 +492,7 @@ window.app = Vue.createApp({ ticks: { color: '#666666', font: { size: 12, weight: '500' }, - callback: function(value) { + callback: function (value) { return value.toLocaleString() + ' sats' } } @@ -498,7 +504,7 @@ window.app = Vue.createApp({ this.chartLoading = false return } - + // Group cost_basis_history by date to eliminate duplicates const groupedData = new Map() chartData.forEach(point => { @@ -513,30 +519,30 @@ window.app = Vue.createApp({ } } }) - - const uniqueChartData = Array.from(groupedData.values()).sort((a, b) => + + const uniqueChartData = Array.from(groupedData.values()).sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime() ) - + const labels = uniqueChartData.map(point => { // Handle different date formats with enhanced timezone handling let date; if (point.date) { console.log('Raw date from API:', point.date); // Debug the actual date string - + // If it's an ISO string with timezone info, parse it correctly if (typeof point.date === 'string' && point.date.includes('T')) { // ISO string - parse and convert to local date date = new Date(point.date); // For display purposes, use the date part only to avoid timezone shifts - const localDateStr = date.getFullYear() + '-' + - String(date.getMonth() + 1).padStart(2, '0') + '-' + - String(date.getDate()).padStart(2, '0'); + const localDateStr = date.getFullYear() + '-' + + String(date.getMonth() + 1).padStart(2, '0') + '-' + + String(date.getDate()).padStart(2, '0'); date = new Date(localDateStr + 'T00:00:00'); // Force local midnight } else { date = new Date(point.date); } - + // Check if date is valid if (isNaN(date.getTime())) { date = new Date(); @@ -544,9 +550,9 @@ window.app = Vue.createApp({ } else { date = new Date(); } - + console.log('Formatted date:', date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })); - + return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' @@ -557,22 +563,22 @@ window.app = Vue.createApp({ 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) { @@ -585,34 +591,34 @@ window.app = Vue.createApp({ createChart(labels, cumulativeSats) { console.log('createChart called with loading state:', this.chartLoading) - + if (!this.$refs.dcaChart) { console.log('Chart ref not available for createChart') return } - + // Skip if we're not in a loading state (indicates this is a stale call) if (!this.chartLoading) { console.log('Not in loading state, skipping createChart') return } - + // Destroy existing chart if (this.dcaChart) { console.log('Destroying existing chart in createChart') this.dcaChart.destroy() this.dcaChart = null } - + const ctx = this.$refs.dcaChart.getContext('2d') - + try { // 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)') - + // Small delay to ensure Chart.js is fully initialized setTimeout(() => { try { @@ -621,7 +627,7 @@ window.app = Vue.createApp({ console.log('Loading state changed during timeout, aborting chart creation') return } - + this.dcaChart = new Chart(ctx, { type: 'line', data: { @@ -662,7 +668,7 @@ window.app = Vue.createApp({ cornerRadius: 8, displayColors: false, callbacks: { - title: function(context) { + title: function (context) { return `📅 ${context[0].label}` }, label: function (context) { @@ -741,10 +747,10 @@ window.app = Vue.createApp({ async created() { try { this.loading = true - + // Check registration status first await this.checkRegistrationStatus() - + // Only load dashboard data if registered if (this.isRegistered) { await Promise.all([ @@ -768,7 +774,7 @@ window.app = Vue.createApp({ 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() @@ -781,6 +787,14 @@ window.app = Vue.createApp({ computed: { hasData() { return this.dashboardData && !this.loading && this.isRegistered + }, + + walletOptions() { + if (!this.g.user?.wallets) return [] + return this.g.user.wallets.map(wallet => ({ + label: `${wallet.name} (${Math.round(wallet.balance_msat / 1000)} sats)`, + value: wallet.id + })) } }, diff --git a/templates/satmachineclient/index.html b/templates/satmachineclient/index.html index 6bbc1b2..b5600c1 100644 --- a/templates/satmachineclient/index.html +++ b/templates/satmachineclient/index.html @@ -7,7 +7,7 @@ {% endblock %} {% block page %} -