diff --git a/app/modules/loyalty/locales/en.json b/app/modules/loyalty/locales/en.json index bee41b67..dc7b1a22 100644 --- a/app/modules/loyalty/locales/en.json +++ b/app/modules/loyalty/locales/en.json @@ -801,7 +801,15 @@ "quick_actions": "Quick Actions", "open_terminal": "Open Terminal", "view_members": "View Members", - "view_program": "View Program" + "view_program": "View Program", + "revenue_title": "Points & Customers", + "at_risk_title": "At-Risk Members", + "cards_at_risk": "members at risk of churn", + "no_at_risk": "All members are active!", + "cohort_title": "Cohort Retention", + "cohort_month": "Enrollment Month", + "cohort_enrolled": "Enrolled", + "no_data_yet": "Not enough data yet. Analytics will appear as customers enroll and transact." }, "program": { "title": "Loyalty Program", diff --git a/app/modules/loyalty/static/store/js/loyalty-analytics.js b/app/modules/loyalty/static/store/js/loyalty-analytics.js index 4240ca73..59a11379 100644 --- a/app/modules/loyalty/static/store/js/loyalty-analytics.js +++ b/app/modules/loyalty/static/store/js/loyalty-analytics.js @@ -24,6 +24,12 @@ function storeLoyaltyAnalytics() { estimated_liability_cents: 0, }, + // Advanced analytics + cohortData: { cohorts: [] }, + churnData: { at_risk_count: 0, cards: [] }, + revenueData: { monthly: [], by_store: [] }, + revenueChart: null, + loading: false, error: null, @@ -56,6 +62,7 @@ function storeLoyaltyAnalytics() { await this.loadProgram(); if (this.program) { await this.loadStats(); + this.loadAdvancedAnalytics(); } loyaltyAnalyticsLog.info('=== LOYALTY ANALYTICS PAGE INITIALIZATION COMPLETE ==='); }, @@ -99,6 +106,72 @@ function storeLoyaltyAnalytics() { } }, + 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); diff --git a/app/modules/loyalty/templates/loyalty/store/analytics.html b/app/modules/loyalty/templates/loyalty/store/analytics.html index ceb832c1..c4f717e8 100644 --- a/app/modules/loyalty/templates/loyalty/store/analytics.html +++ b/app/modules/loyalty/templates/loyalty/store/analytics.html @@ -46,6 +46,90 @@ {% set show_merchants_metric = false %} {% include "loyalty/shared/analytics-stats.html" %} + +
+ +
+

+ + {{ _('loyalty.store.analytics.revenue_title') }} +

+
+ +
+

+ {{ _('loyalty.store.analytics.no_data_yet') }} +

+
+ + +
+

+ + {{ _('loyalty.store.analytics.at_risk_title') }} +

+
+

+ + {{ _('loyalty.store.analytics.cards_at_risk') }} +

+
+ +
+
+

+ {{ _('loyalty.store.analytics.no_at_risk') }} +

+
+
+ + +
+

+ + {{ _('loyalty.store.analytics.cohort_title') }} +

+
+ + + + + + + + + + + +
{{ _('loyalty.store.analytics.cohort_month') }}{{ _('loyalty.store.analytics.cohort_enrolled') }}
+
+

+ {{ _('loyalty.store.analytics.no_data_yet') }} +

+
+

{{ _('loyalty.store.analytics.quick_actions') }}

@@ -71,5 +155,7 @@ {% endblock %} {% block extra_scripts %} +{% include 'shared/includes/optional-libs.html' with context %} +{{ chartjs_loader() }} {% endblock %}