// app/modules/analytics/static/store/js/analytics.js /** * Store analytics and reports page logic * View business metrics and performance data */ const storeAnalyticsLog = window.LogConfig.loggers.storeAnalytics || window.LogConfig.createLogger('storeAnalytics', false); storeAnalyticsLog.info('Loading...'); function storeAnalytics() { storeAnalyticsLog.info('storeAnalytics() called'); return { // Inherit base layout state ...data(), // Set page identifier currentPage: 'analytics', // Loading states loading: true, error: '', // Time period period: '30d', periodOptions: [ { value: '7d', label: 'Last 7 Days' }, { value: '30d', label: 'Last 30 Days' }, { value: '90d', label: 'Last 90 Days' }, { value: '1y', label: 'Last Year' } ], // Analytics data analytics: null, stats: null, // Dashboard stats (from store stats endpoint) dashboardStats: { total_products: 0, active_products: 0, featured_products: 0, total_orders: 0, pending_orders: 0, total_customers: 0, total_inventory: 0, low_stock_count: 0 }, async init() { storeAnalyticsLog.info('Analytics init() called'); // Guard against multiple initialization if (window._storeAnalyticsInitialized) { storeAnalyticsLog.warn('Already initialized, skipping'); return; } window._storeAnalyticsInitialized = true; // IMPORTANT: Call parent init first to set storeCode from URL const parentInit = data().init; if (parentInit) { await parentInit.call(this); } try { await this.loadAllData(); } catch (error) { storeAnalyticsLog.error('Init failed:', error); this.error = 'Failed to initialize analytics page'; } finally { this.loading = false; } storeAnalyticsLog.info('Analytics initialization complete'); }, /** * Load all analytics data */ async loadAllData() { this.loading = true; this.error = ''; try { // Load analytics and stats in parallel const [analyticsResponse, statsResponse] = await Promise.all([ this.fetchAnalytics(), this.fetchStats() ]); this.analytics = analyticsResponse; this.dashboardStats = statsResponse; storeAnalyticsLog.info('Loaded analytics data'); } catch (error) { storeAnalyticsLog.error('Failed to load data:', error); this.error = error.message || 'Failed to load analytics data'; } finally { this.loading = false; } }, /** * Fetch analytics data for current period */ async fetchAnalytics() { try { const response = await apiClient.get(`/store/analytics?period=${this.period}`); return response; } catch (error) { // Analytics might require feature access if (error.status === 403) { storeAnalyticsLog.warn('Analytics feature not available'); return null; } throw error; } }, /** * Fetch dashboard stats */ async fetchStats() { try { const response = await apiClient.get(`/store/dashboard/stats`); return { total_products: response.catalog?.total_products || 0, active_products: response.catalog?.active_products || 0, featured_products: response.catalog?.featured_products || 0, total_orders: response.orders?.total || 0, pending_orders: response.orders?.pending || 0, total_customers: response.customers?.total || 0, total_inventory: response.inventory?.total_quantity || 0, low_stock_count: response.inventory?.low_stock_count || 0 }; } catch (error) { storeAnalyticsLog.error('Failed to fetch stats:', error); return this.dashboardStats; } }, /** * Change time period and reload data */ async changePeriod(newPeriod) { this.period = newPeriod; try { await this.loadAllData(); } catch (error) { storeAnalyticsLog.error('Failed to change period:', error); } }, /** * Get period label */ getPeriodLabel() { const option = this.periodOptions.find(p => p.value === this.period); return option ? option.label : this.period; }, /** * Format number with commas */ formatNumber(num) { if (num === null || num === undefined) return '0'; const locale = window.STORE_CONFIG?.locale || 'en-GB'; return num.toLocaleString(locale); }, /** * Format percentage */ formatPercent(value) { if (value === null || value === undefined) return '0%'; return `${value.toFixed(1)}%`; }, /** * Calculate active product percentage */ get activeProductPercent() { if (!this.dashboardStats.total_products) return 0; return (this.dashboardStats.active_products / this.dashboardStats.total_products * 100).toFixed(1); }, /** * Calculate pending order percentage */ get pendingOrderPercent() { if (!this.dashboardStats.total_orders) return 0; return (this.dashboardStats.pending_orders / this.dashboardStats.total_orders * 100).toFixed(1); }, /** * Get stock health status */ get stockHealth() { if (this.dashboardStats.low_stock_count === 0) { return { status: 'good', label: 'Healthy', color: 'green' }; } else if (this.dashboardStats.low_stock_count <= 5) { return { status: 'warning', label: 'Attention Needed', color: 'yellow' }; } else { return { status: 'critical', label: 'Critical', color: 'red' }; } } }; }