// app/modules/loyalty/static/shared/js/loyalty-card-detail-view.js // Shared Alpine.js data factory for loyalty card detail pages. // Used by store, merchant, and admin frontends. const loyaltyCardDetailViewLog = window.LogConfig.loggers.loyaltyCardDetailView || window.LogConfig.createLogger('loyaltyCardDetailView'); /** * Factory that returns an Alpine.js data object for a single loyalty card detail view. * * @param {Object} config * @param {string} config.apiPrefix - API path prefix, e.g. '/store/loyalty' * @param {string} config.backUrl - URL for the "back to list" navigation * @param {string} config.currentPage - Alpine currentPage identifier */ function loyaltyCardDetailView(config) { const guardKey = '_loyaltyCardDetailView_' + config.currentPage + '_initialized'; return { ...data(), currentPage: config.currentPage, // Data cardId: null, card: null, transactions: [], // State loading: false, error: null, // Config (exposed for templates) _config: config, async init() { loyaltyCardDetailViewLog.info('=== LOYALTY CARD DETAIL VIEW INITIALIZING ===', config.currentPage); if (window[guardKey]) return; window[guardKey] = true; const parentInit = data().init; if (parentInit) { await parentInit.call(this); } // Extract cardId from URL: find 'cards' segment and take the next segment this.cardId = this._extractCardIdFromUrl(); if (!this.cardId) { this.error = I18n.t('loyalty.errors.card_not_found'); loyaltyCardDetailViewLog.error('Could not extract card ID from URL'); return; } loyaltyCardDetailViewLog.info('Card ID extracted:', this.cardId); await this.loadData(); loyaltyCardDetailViewLog.info('=== LOYALTY CARD DETAIL VIEW INITIALIZATION COMPLETE ==='); }, /** * Extract the card ID from the current URL path. * Looks for the 'cards' segment and returns the segment immediately after it. * e.g. /store/ORION/loyalty/cards/abc123 -> 'abc123' */ _extractCardIdFromUrl() { try { const segments = window.location.pathname.split('/').filter(Boolean); const cardsIndex = segments.indexOf('cards'); if (cardsIndex !== -1 && cardsIndex + 1 < segments.length) { return segments[cardsIndex + 1]; } } catch (error) { loyaltyCardDetailViewLog.error('Error parsing URL for card ID:', error); } return null; }, async loadData() { this.loading = true; this.error = null; try { await Promise.all([ this.loadCard(), this.loadTransactions() ]); } catch (error) { loyaltyCardDetailViewLog.error('Failed to load card data:', error); this.error = error.message; } finally { this.loading = false; } }, async loadCard() { try { const response = await apiClient.get(config.apiPrefix + '/cards/' + this.cardId); if (response) { this.card = response; } } catch (error) { loyaltyCardDetailViewLog.error('Failed to load card:', error); throw error; } }, async loadTransactions() { try { const response = await apiClient.get( config.apiPrefix + '/cards/' + this.cardId + '/transactions?limit=50' ); if (response) { this.transactions = Array.isArray(response) ? response : (response.transactions || []); } } catch (error) { loyaltyCardDetailViewLog.warn('Failed to load transactions:', error.message); } }, // Formatting helpers formatNumber(num) { return num == null ? '0' : new Intl.NumberFormat('en-US').format(num); }, formatDate(dateString) { if (!dateString) return 'Never'; try { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); } catch (e) { return dateString; } }, formatDateTime(dateString) { if (!dateString) return 'Never'; try { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } catch (e) { return dateString; } } }; } if (!window.LogConfig.loggers.loyaltyCardDetailView) { window.LogConfig.loggers.loyaltyCardDetailView = window.LogConfig.createLogger('loyaltyCardDetailView'); } loyaltyCardDetailViewLog.info('Loyalty card detail view shared module loaded');