feat(loyalty): refactor analytics into shared template and add merchant stats API
Some checks failed
Some checks failed
Extract analytics stat cards, points activity, and location breakdown into a shared partial used by admin, merchant, and store dashboards. Add merchant stats API endpoint and client-side merchant filter on admin analytics page. Extend stats schema with new_this_month and estimated_liability_cents fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,18 +20,34 @@ function storeLoyaltyAnalytics() {
|
||||
points_issued_30d: 0,
|
||||
points_redeemed_30d: 0,
|
||||
transactions_30d: 0,
|
||||
avg_points_per_member: 0
|
||||
avg_points_per_member: 0,
|
||||
estimated_liability_cents: 0,
|
||||
},
|
||||
|
||||
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;
|
||||
|
||||
// IMPORTANT: Call parent init first to set storeCode from URL
|
||||
// Call parent init to set storeCode from URL
|
||||
const parentInit = data().init;
|
||||
if (parentInit) {
|
||||
await parentInit.call(this);
|
||||
@@ -70,7 +86,8 @@ function storeLoyaltyAnalytics() {
|
||||
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
|
||||
avg_points_per_member: response.avg_points_per_member || 0,
|
||||
estimated_liability_cents: response.estimated_liability_cents || 0,
|
||||
};
|
||||
loyaltyAnalyticsLog.info('Stats loaded');
|
||||
}
|
||||
@@ -83,7 +100,8 @@ function storeLoyaltyAnalytics() {
|
||||
},
|
||||
|
||||
formatNumber(num) {
|
||||
return num == null ? '0' : new Intl.NumberFormat('en-US').format(num);
|
||||
if (num === null || num === undefined) return '0';
|
||||
return new Intl.NumberFormat('en-US').format(num);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user