Files
orion/app/modules/customers/static/store/js/customer-detail.js
Samir Boulahtit 34bf961309 fix: resolve all 19 architecture validator warnings
- API-004: Add noqa for factory-pattern auth in user_account routes and payments admin
- MDL-003: Add from_attributes to MerchantStoreDetailResponse schema
- EXC-003: Suppress broad except in merchant_store_service and admin_subscription_service
  (intentional fallbacks for optional billing module)
- NAM-002: Rename onboarding files to *_service.py suffix and update all imports
- JS-001: Add file-level noqa for dev-toolbar.js (console interceptor by design)
- JS-005: Add init guards to dashboard.js and customer-detail.js
- IMPORT-004: Break circular deps by removing orders from inventory requires and
  marketplace from orders requires; add IMPORT-002 suppression for lazy cross-imports
- MOD-025: Remove unused OnboardingAlreadyCompletedException

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 23:43:12 +01:00

154 lines
4.8 KiB
JavaScript

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