// app/modules/customers/static/store/js/customer-detail.js /** * Store customer detail page logic. * Loads customer profile, order stats, and recent orders from existing APIs. */ const customerDetailLog = window.LogConfig?.createLogger('customerDetail') || console; function storeCustomerDetail() { return { // Inherit base layout state ...data(), // Page identifier currentPage: 'customers', // Loading states loading: true, error: '', // Data customerId: window.customerDetailData?.customerId, customer: null, orderStats: { total_orders: 0, total_spent_cents: 0, last_order_date: null, first_order_date: null }, recentOrders: [], // Computed get customerName() { if (this.customer?.first_name && this.customer?.last_name) { return `${this.customer.first_name} ${this.customer.last_name}`; } return this.customer?.email || 'Unknown'; }, async init() { if (window._customerDetailInitialized) return; window._customerDetailInitialized = true; try { // Load i18n translations await I18n.loadModule('customers'); customerDetailLog.info('Customer detail init, id:', this.customerId); // Call parent init to set storeCode from URL const parentInit = data().init; if (parentInit) { await parentInit.call(this); } // Load all data in parallel await Promise.all([ this.loadCustomer(), this.loadOrderStats(), this.loadRecentOrders() ]); customerDetailLog.info('Customer detail loaded'); } catch (error) { customerDetailLog.error('Init failed:', error); this.error = 'Failed to load customer details'; } finally { this.loading = false; } }, /** * Load customer profile */ async loadCustomer() { try { const response = await apiClient.get(`/store/customers/${this.customerId}`); this.customer = response; } catch (error) { customerDetailLog.error('Failed to load customer:', error); this.error = error.message || 'Customer not found'; } }, /** * Load order statistics from orders module */ async loadOrderStats() { try { const response = await apiClient.get(`/store/customers/${this.customerId}/order-stats`); this.orderStats = response; } catch (error) { customerDetailLog.warn('Failed to load order stats:', error); // Non-fatal — page still works without stats } }, /** * Load recent orders from orders module */ async loadRecentOrders() { try { const response = await apiClient.get(`/store/customers/${this.customerId}/orders?limit=5`); this.recentOrders = response.orders || []; } catch (error) { customerDetailLog.warn('Failed to load recent orders:', error); // Non-fatal } }, /** * Get customer initials for avatar */ getInitials() { const first = this.customer?.first_name || ''; const last = this.customer?.last_name || ''; return (first.charAt(0) + last.charAt(0)).toUpperCase() || '?'; }, /** * Navigate to send message */ messageCustomer() { window.location.href = `/store/${this.storeCode}/messages?customer=${this.customerId}`; }, /** * Format date for display */ formatDate(dateStr) { if (!dateStr) return '-'; const locale = window.STORE_CONFIG?.locale || 'en-GB'; return new Date(dateStr).toLocaleDateString(locale, { year: 'numeric', month: 'short', day: 'numeric' }); }, /** * Format price (cents to currency) */ formatPrice(cents) { if (!cents && cents !== 0) return '-'; const locale = window.STORE_CONFIG?.locale || 'en-GB'; const currency = window.STORE_CONFIG?.currency || 'EUR'; return new Intl.NumberFormat(locale, { style: 'currency', currency: currency }).format(cents / 100); } }; }