Files
orion/static/vendor/js/letzshop.js
Samir Boulahtit d2b05441fc feat: add multi-language (i18n) support for vendor dashboard and storefront
- Add database fields for language preferences:
  - Vendor: dashboard_language, storefront_language, storefront_languages
  - User: preferred_language
  - Customer: preferred_language

- Add language middleware for request-level language detection:
  - Cookie-based persistence
  - Browser Accept-Language fallback
  - Vendor storefront language constraints

- Add language API endpoints (/api/v1/language/*):
  - POST /set - Set language preference
  - GET /current - Get current language info
  - GET /list - List available languages
  - DELETE /clear - Clear preference

- Add i18n utilities (app/utils/i18n.py):
  - JSON-based translation loading
  - Jinja2 template integration
  - Language resolution helpers

- Add reusable language selector macros for templates
- Add languageSelector() Alpine.js component
- Add translation files (en, fr, de, lb) in static/locales/
- Add architecture rules documentation for language implementation
- Update marketplace-product-detail.js to use native language names

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 22:36:09 +01:00

483 lines
16 KiB
JavaScript

// static/vendor/js/letzshop.js
/**
* Vendor Letzshop orders management page logic
*/
console.log('[VENDOR LETZSHOP] Loading...');
function vendorLetzshop() {
console.log('[VENDOR LETZSHOP] vendorLetzshop() called');
return {
// Inherit base layout state
...data(),
// Set page identifier
currentPage: 'letzshop',
// Tab state
activeTab: 'orders',
// Loading states
loading: false,
importing: false,
saving: false,
testing: false,
submittingTracking: false,
// Messages
error: '',
successMessage: '',
// Integration status
status: {
is_configured: false,
is_connected: false,
auto_sync_enabled: false,
last_sync_at: null,
last_sync_status: null
},
// Credentials
credentials: null,
credentialsForm: {
api_key: '',
auto_sync_enabled: false,
sync_interval_minutes: 15
},
showApiKey: false,
// Orders
orders: [],
totalOrders: 0,
page: 1,
limit: 20,
filters: {
sync_status: ''
},
// Order stats
orderStats: {
pending: 0,
confirmed: 0,
rejected: 0,
shipped: 0
},
// Modals
showTrackingModal: false,
showOrderModal: false,
selectedOrder: null,
trackingForm: {
tracking_number: '',
tracking_carrier: ''
},
// Export
exportLanguage: 'fr',
exportIncludeInactive: false,
exporting: false,
async init() {
// Guard against multiple initialization
if (window._vendorLetzshopInitialized) {
return;
}
window._vendorLetzshopInitialized = true;
// Call parent init first to set vendorCode from URL
const parentInit = data().init;
if (parentInit) {
await parentInit.call(this);
}
await this.loadStatus();
await this.loadOrders();
},
/**
* Load integration status
*/
async loadStatus() {
try {
const response = await apiClient.get('/vendor/letzshop/status');
this.status = response;
if (this.status.is_configured) {
await this.loadCredentials();
}
} catch (error) {
console.error('[VENDOR LETZSHOP] Failed to load status:', error);
}
},
/**
* Load credentials (masked)
*/
async loadCredentials() {
try {
const response = await apiClient.get('/vendor/letzshop/credentials');
this.credentials = response;
this.credentialsForm.auto_sync_enabled = response.auto_sync_enabled;
this.credentialsForm.sync_interval_minutes = response.sync_interval_minutes;
} catch (error) {
// 404 means not configured, which is fine
if (error.status !== 404) {
console.error('[VENDOR LETZSHOP] Failed to load credentials:', error);
}
}
},
/**
* Load orders
*/
async loadOrders() {
this.loading = true;
this.error = '';
try {
const params = new URLSearchParams({
skip: ((this.page - 1) * this.limit).toString(),
limit: this.limit.toString()
});
if (this.filters.sync_status) {
params.append('sync_status', this.filters.sync_status);
}
const response = await apiClient.get(`/vendor/letzshop/orders?${params}`);
this.orders = response.orders;
this.totalOrders = response.total;
// Calculate stats
await this.loadOrderStats();
} catch (error) {
console.error('[VENDOR LETZSHOP] Failed to load orders:', error);
this.error = error.message || 'Failed to load orders';
} finally {
this.loading = false;
}
},
/**
* Load order stats by fetching counts for each status
*/
async loadOrderStats() {
try {
// Get all orders without filter to calculate stats
const allResponse = await apiClient.get('/vendor/letzshop/orders?limit=1000');
const allOrders = allResponse.orders || [];
this.orderStats = {
pending: allOrders.filter(o => o.sync_status === 'pending').length,
confirmed: allOrders.filter(o => o.sync_status === 'confirmed').length,
rejected: allOrders.filter(o => o.sync_status === 'rejected').length,
shipped: allOrders.filter(o => o.sync_status === 'shipped').length
};
} catch (error) {
console.error('[VENDOR LETZSHOP] Failed to load order stats:', error);
}
},
/**
* Refresh all data
*/
async refreshData() {
await this.loadStatus();
await this.loadOrders();
this.successMessage = 'Data refreshed';
setTimeout(() => this.successMessage = '', 3000);
},
/**
* Import orders from Letzshop
*/
async importOrders() {
if (!this.status.is_configured) {
this.error = 'Please configure your API key first';
this.activeTab = 'settings';
return;
}
this.importing = true;
this.error = '';
try {
const response = await apiClient.post('/vendor/letzshop/orders/import', {
operation: 'order_import'
});
if (response.success) {
this.successMessage = response.message;
await this.loadOrders();
} else {
this.error = response.message || 'Import failed';
}
} catch (error) {
console.error('[VENDOR LETZSHOP] Import failed:', error);
this.error = error.message || 'Failed to import orders';
} finally {
this.importing = false;
setTimeout(() => this.successMessage = '', 5000);
}
},
/**
* Save credentials
*/
async saveCredentials() {
if (!this.credentialsForm.api_key && !this.credentials) {
this.error = 'Please enter an API key';
return;
}
this.saving = true;
this.error = '';
try {
const payload = {
auto_sync_enabled: this.credentialsForm.auto_sync_enabled,
sync_interval_minutes: parseInt(this.credentialsForm.sync_interval_minutes)
};
if (this.credentialsForm.api_key) {
payload.api_key = this.credentialsForm.api_key;
}
const response = await apiClient.post('/vendor/letzshop/credentials', payload);
this.credentials = response;
this.credentialsForm.api_key = '';
this.status.is_configured = true;
this.successMessage = 'Credentials saved successfully';
} catch (error) {
console.error('[VENDOR LETZSHOP] Failed to save credentials:', error);
this.error = error.message || 'Failed to save credentials';
} finally {
this.saving = false;
setTimeout(() => this.successMessage = '', 5000);
}
},
/**
* Test connection
*/
async testConnection() {
this.testing = true;
this.error = '';
try {
const response = await apiClient.post('/vendor/letzshop/test');
if (response.success) {
this.successMessage = `Connection successful (${response.response_time_ms?.toFixed(0)}ms)`;
} else {
this.error = response.error_details || 'Connection failed';
}
} catch (error) {
console.error('[VENDOR LETZSHOP] Connection test failed:', error);
this.error = error.message || 'Connection test failed';
} finally {
this.testing = false;
setTimeout(() => this.successMessage = '', 5000);
}
},
/**
* Delete credentials
*/
async deleteCredentials() {
if (!confirm('Are you sure you want to remove your Letzshop credentials?')) {
return;
}
try {
await apiClient.delete('/vendor/letzshop/credentials');
this.credentials = null;
this.status.is_configured = false;
this.credentialsForm = {
api_key: '',
auto_sync_enabled: false,
sync_interval_minutes: 15
};
this.successMessage = 'Credentials removed';
} catch (error) {
console.error('[VENDOR LETZSHOP] Failed to delete credentials:', error);
this.error = error.message || 'Failed to remove credentials';
}
setTimeout(() => this.successMessage = '', 5000);
},
/**
* Confirm order
*/
async confirmOrder(order) {
if (!confirm('Confirm this order?')) {
return;
}
try {
const response = await apiClient.post(`/vendor/letzshop/orders/${order.id}/confirm`);
if (response.success) {
this.successMessage = 'Order confirmed';
await this.loadOrders();
} else {
this.error = response.message || 'Failed to confirm order';
}
} catch (error) {
console.error('[VENDOR LETZSHOP] Failed to confirm order:', error);
this.error = error.message || 'Failed to confirm order';
}
setTimeout(() => this.successMessage = '', 5000);
},
/**
* Reject order
*/
async rejectOrder(order) {
if (!confirm('Reject this order? This action cannot be undone.')) {
return;
}
try {
const response = await apiClient.post(`/vendor/letzshop/orders/${order.id}/reject`);
if (response.success) {
this.successMessage = 'Order rejected';
await this.loadOrders();
} else {
this.error = response.message || 'Failed to reject order';
}
} catch (error) {
console.error('[VENDOR LETZSHOP] Failed to reject order:', error);
this.error = error.message || 'Failed to reject order';
}
setTimeout(() => this.successMessage = '', 5000);
},
/**
* Open tracking modal
*/
openTrackingModal(order) {
this.selectedOrder = order;
this.trackingForm = {
tracking_number: order.tracking_number || '',
tracking_carrier: order.tracking_carrier || ''
};
this.showTrackingModal = true;
},
/**
* Submit tracking
*/
async submitTracking() {
if (!this.trackingForm.tracking_number || !this.trackingForm.tracking_carrier) {
this.error = 'Please fill in all fields';
return;
}
this.submittingTracking = true;
try {
const response = await apiClient.post(
`/vendor/letzshop/orders/${this.selectedOrder.id}/tracking`,
this.trackingForm
);
if (response.success) {
this.showTrackingModal = false;
this.successMessage = 'Tracking information saved';
await this.loadOrders();
} else {
this.error = response.message || 'Failed to save tracking';
}
} catch (error) {
console.error('[VENDOR LETZSHOP] Failed to set tracking:', error);
this.error = error.message || 'Failed to save tracking';
} finally {
this.submittingTracking = false;
}
setTimeout(() => this.successMessage = '', 5000);
},
/**
* View order details
*/
viewOrderDetails(order) {
this.selectedOrder = order;
this.showOrderModal = true;
},
/**
* Format date for display
*/
formatDate(dateStr) {
if (!dateStr) return 'N/A';
const date = new Date(dateStr);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
},
/**
* Download product export CSV
*/
async downloadExport() {
this.exporting = true;
this.error = '';
try {
const params = new URLSearchParams({
language: this.exportLanguage,
include_inactive: this.exportIncludeInactive.toString()
});
// Get the token for authentication
const token = localStorage.getItem('wizamart_token');
if (!token) {
throw new Error('Not authenticated');
}
const response = await fetch(`/api/v1/vendor/letzshop/export?${params}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || 'Export failed');
}
// Get filename from Content-Disposition header
const contentDisposition = response.headers.get('Content-Disposition');
let filename = `letzshop_export_${this.exportLanguage}.csv`;
if (contentDisposition) {
const match = contentDisposition.match(/filename="(.+)"/);
if (match) {
filename = match[1];
}
}
// Download the file
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
this.successMessage = `Export downloaded: ${filename}`;
} catch (error) {
console.error('[VENDOR LETZSHOP] Export failed:', error);
this.error = error.message || 'Failed to export products';
} finally {
this.exporting = false;
setTimeout(() => this.successMessage = '', 5000);
}
}
};
}