feat(loyalty): refactor analytics into shared template and add merchant stats API
Some checks failed
CI / ruff (push) Successful in 11s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled

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:
2026-03-11 11:08:16 +01:00
parent 8cf5da6914
commit 6acd783754
11 changed files with 630 additions and 375 deletions

View File

@@ -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);
}
};
}