Files
orion/app/modules/loyalty/static/vendor/js/loyalty-card-detail.js
Samir Boulahtit d8f3338bc8 feat(loyalty): implement Phase 2 - company-wide points system
Complete implementation of loyalty module Phase 2 features:

Database & Models:
- Add company_id to LoyaltyProgram for chain-wide loyalty
- Add company_id to LoyaltyCard for multi-location support
- Add CompanyLoyaltySettings model for admin-controlled settings
- Add points expiration, welcome bonus, and minimum redemption fields
- Add POINTS_EXPIRED, WELCOME_BONUS transaction types

Services:
- Update program_service for company-based queries
- Update card_service with enrollment and welcome bonus
- Update points_service with void_points for returns
- Update stamp_service for company context
- Update pin_service for company-wide operations

API Endpoints:
- Admin: Program listing with stats, company detail views
- Vendor: Terminal operations, card management, settings
- Storefront: Customer card/transactions, self-enrollment

UI Templates:
- Admin: Programs dashboard, company detail, settings
- Vendor: Terminal, cards list, card detail, settings, stats, enrollment
- Storefront: Dashboard, history, enrollment, success pages

Background Tasks:
- Point expiration task (daily, based on inactivity)
- Wallet sync task (hourly)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 22:10:27 +01:00

105 lines
3.6 KiB
JavaScript

// app/modules/loyalty/static/vendor/js/loyalty-card-detail.js
// noqa: js-006 - async init pattern is safe, loadData has try/catch
const loyaltyCardDetailLog = window.LogConfig.loggers.loyaltyCardDetail || window.LogConfig.createLogger('loyaltyCardDetail');
function vendorLoyaltyCardDetail() {
return {
...data(),
currentPage: 'loyalty-card-detail',
cardId: null,
card: null,
transactions: [],
loading: false,
error: null,
async init() {
loyaltyCardDetailLog.info('=== LOYALTY CARD DETAIL PAGE INITIALIZING ===');
if (window._loyaltyCardDetailInitialized) return;
window._loyaltyCardDetailInitialized = true;
// Extract card ID from URL
const pathParts = window.location.pathname.split('/');
const cardsIndex = pathParts.indexOf('cards');
if (cardsIndex !== -1 && pathParts[cardsIndex + 1]) {
this.cardId = parseInt(pathParts[cardsIndex + 1]);
}
if (!this.cardId) {
this.error = 'Invalid card ID';
return;
}
await this.loadData();
loyaltyCardDetailLog.info('=== LOYALTY CARD DETAIL PAGE INITIALIZATION COMPLETE ===');
},
async loadData() {
this.loading = true;
this.error = null;
try {
await Promise.all([
this.loadCard(),
this.loadTransactions()
]);
} catch (error) {
loyaltyCardDetailLog.error('Failed to load data:', error);
this.error = error.message;
} finally {
this.loading = false;
}
},
async loadCard() {
const response = await apiClient.get(`/vendor/loyalty/cards/${this.cardId}`);
if (response) {
this.card = response;
loyaltyCardDetailLog.info('Card loaded:', this.card.card_number);
}
},
async loadTransactions() {
try {
const response = await apiClient.get(`/vendor/loyalty/cards/${this.cardId}/transactions?limit=50`);
if (response && response.transactions) {
this.transactions = response.transactions;
loyaltyCardDetailLog.info(`Loaded ${this.transactions.length} transactions`);
}
} catch (error) {
loyaltyCardDetailLog.warn('Failed to load transactions:', error.message);
}
},
formatNumber(num) {
return num == null ? '0' : new Intl.NumberFormat('en-US').format(num);
},
formatDate(dateString) {
if (!dateString) return '-';
try {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric', month: 'short', day: 'numeric'
});
} catch (e) { return dateString; }
},
formatDateTime(dateString) {
if (!dateString) return '-';
try {
return new Date(dateString).toLocaleString('en-US', {
year: 'numeric', month: 'short', day: 'numeric',
hour: '2-digit', minute: '2-digit'
});
} catch (e) { return dateString; }
}
};
}
if (!window.LogConfig.loggers.loyaltyCardDetail) {
window.LogConfig.loggers.loyaltyCardDetail = window.LogConfig.createLogger('loyaltyCardDetail');
}
loyaltyCardDetailLog.info('Loyalty card detail module loaded');