// app/modules/loyalty/static/store/js/loyalty-analytics.js // noqa: js-006 - async init pattern is safe, loadData has try/catch const loyaltyAnalyticsLog = window.LogConfig.loggers.loyaltyAnalytics || window.LogConfig.createLogger('loyaltyAnalytics'); function storeLoyaltyAnalytics() { return { ...data(), currentPage: 'analytics', program: null, stats: { total_cards: 0, active_cards: 0, new_this_month: 0, total_points_issued: 0, total_points_redeemed: 0, total_points_balance: 0, points_issued_30d: 0, points_redeemed_30d: 0, transactions_30d: 0, avg_points_per_member: 0, estimated_liability_cents: 0, }, // Advanced analytics cohortData: { cohorts: [] }, churnData: { at_risk_count: 0, cards: [] }, revenueData: { monthly: [], by_store: [] }, revenueChart: null, loading: false, error: null, get redemptionRate() { if (this.stats.points_issued_30d === 0) return 0; return Math.round((this.stats.points_redeemed_30d / this.stats.points_issued_30d) * 100); }, get issuedPercentage() { const total = this.stats.points_issued_30d + this.stats.points_redeemed_30d; if (total === 0) return 50; return Math.round((this.stats.points_issued_30d / total) * 100); }, get redeemedPercentage() { return 100 - this.issuedPercentage; }, async init() { loyaltyAnalyticsLog.info('=== LOYALTY ANALYTICS PAGE INITIALIZING ==='); if (window._loyaltyAnalyticsInitialized) return; window._loyaltyAnalyticsInitialized = true; // Call parent init to set storeCode from URL const parentInit = data().init; if (parentInit) { await parentInit.call(this); } await this.loadProgram(); if (this.program) { await this.loadStats(); this.loadAdvancedAnalytics(); } loyaltyAnalyticsLog.info('=== LOYALTY ANALYTICS PAGE INITIALIZATION COMPLETE ==='); }, async loadProgram() { try { const response = await apiClient.get('/store/loyalty/program'); if (response) this.program = response; } catch (error) { if (error.status !== 404) throw error; } }, async loadStats() { this.loading = true; this.error = null; try { const response = await apiClient.get('/store/loyalty/stats'); if (response) { this.stats = { total_cards: response.total_cards || 0, active_cards: response.active_cards || 0, new_this_month: response.new_this_month || 0, total_points_issued: response.total_points_issued || 0, total_points_redeemed: response.total_points_redeemed || 0, total_points_balance: response.total_points_balance || 0, points_issued_30d: response.points_issued_30d || 0, points_redeemed_30d: response.points_redeemed_30d || 0, transactions_30d: response.transactions_30d || 0, avg_points_per_member: response.avg_points_per_member || 0, estimated_liability_cents: response.estimated_liability_cents || 0, }; loyaltyAnalyticsLog.info('Stats loaded'); } } catch (error) { loyaltyAnalyticsLog.error('Failed to load stats:', error); this.error = error.message; } finally { this.loading = false; } }, async loadAdvancedAnalytics() { try { const [cohort, churn, revenue] = await Promise.all([ apiClient.get('/store/loyalty/analytics/cohorts'), apiClient.get('/store/loyalty/analytics/churn'), apiClient.get('/store/loyalty/analytics/revenue'), ]); if (cohort) this.cohortData = cohort; if (churn) this.churnData = churn; if (revenue) { this.revenueData = revenue; this.$nextTick(() => this.renderRevenueChart()); } loyaltyAnalyticsLog.info('Advanced analytics loaded'); } catch (error) { loyaltyAnalyticsLog.warn('Advanced analytics failed:', error.message); } }, renderRevenueChart() { const canvas = document.getElementById('revenueChart'); if (!canvas || !window.Chart || !this.revenueData.monthly.length) return; if (this.revenueChart) this.revenueChart.destroy(); const labels = this.revenueData.monthly.map(m => m.month); const pointsData = this.revenueData.monthly.map(m => m.total_points_earned); const customersData = this.revenueData.monthly.map(m => m.unique_customers); this.revenueChart = new Chart(canvas, { type: 'bar', data: { labels, datasets: [ { label: 'Points Earned', data: pointsData, backgroundColor: 'rgba(99, 102, 241, 0.7)', borderRadius: 4, yAxisID: 'y', }, { label: 'Active Customers', data: customersData, type: 'line', borderColor: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.1)', fill: true, tension: 0.3, yAxisID: 'y1', }, ], }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, plugins: { legend: { position: 'bottom' } }, scales: { y: { position: 'left', title: { display: true, text: 'Points' } }, y1: { position: 'right', title: { display: true, text: 'Customers' }, grid: { drawOnChartArea: false } }, }, }, }); }, formatNumber(num) { if (num === null || num === undefined) return '0'; return new Intl.NumberFormat('en-US').format(num); } }; } if (!window.LogConfig.loggers.loyaltyAnalytics) { window.LogConfig.loggers.loyaltyAnalytics = window.LogConfig.createLogger('loyaltyAnalytics'); } loyaltyAnalyticsLog.info('Loyalty analytics module loaded');