// static/vendor/js/orders.js /** * Vendor orders management page logic * View and manage vendor's orders */ const vendorOrdersLog = window.LogConfig.loggers.vendorOrders || window.LogConfig.createLogger('vendorOrders', false); vendorOrdersLog.info('Loading...'); function vendorOrders() { vendorOrdersLog.info('vendorOrders() called'); return { // Inherit base layout state ...data(), // Set page identifier currentPage: 'orders', // Loading states loading: true, error: '', saving: false, // Orders data orders: [], stats: { total: 0, pending: 0, processing: 0, completed: 0, cancelled: 0 }, // Order statuses for filter and display statuses: [ { value: 'pending', label: 'Pending', color: 'yellow' }, { value: 'processing', label: 'Processing', color: 'blue' }, { value: 'partially_shipped', label: 'Partially Shipped', color: 'orange' }, { value: 'shipped', label: 'Shipped', color: 'indigo' }, { value: 'delivered', label: 'Delivered', color: 'green' }, { value: 'completed', label: 'Completed', color: 'green' }, { value: 'cancelled', label: 'Cancelled', color: 'red' }, { value: 'refunded', label: 'Refunded', color: 'gray' } ], // Filters filters: { search: '', status: '', date_from: '', date_to: '' }, // Pagination pagination: { page: 1, per_page: 20, total: 0, pages: 0 }, // Modal states showDetailModal: false, showStatusModal: false, showBulkStatusModal: false, selectedOrder: null, newStatus: '', bulkStatus: '', // Bulk selection selectedOrders: [], // Debounce timer searchTimeout: null, // Computed: Total pages get totalPages() { return this.pagination.pages; }, // Computed: Start index for pagination display get startIndex() { if (this.pagination.total === 0) return 0; return (this.pagination.page - 1) * this.pagination.per_page + 1; }, // Computed: End index for pagination display get endIndex() { const end = this.pagination.page * this.pagination.per_page; return end > this.pagination.total ? this.pagination.total : end; }, // Computed: Page numbers for pagination get pageNumbers() { const pages = []; const totalPages = this.totalPages; const current = this.pagination.page; if (totalPages <= 7) { for (let i = 1; i <= totalPages; i++) { pages.push(i); } } else { pages.push(1); if (current > 3) pages.push('...'); const start = Math.max(2, current - 1); const end = Math.min(totalPages - 1, current + 1); for (let i = start; i <= end; i++) { pages.push(i); } if (current < totalPages - 2) pages.push('...'); pages.push(totalPages); } return pages; }, // Computed: Check if all visible orders are selected get allSelected() { return this.orders.length > 0 && this.selectedOrders.length === this.orders.length; }, // Computed: Check if some but not all orders are selected get someSelected() { return this.selectedOrders.length > 0 && this.selectedOrders.length < this.orders.length; }, async init() { vendorOrdersLog.info('Orders init() called'); // Guard against multiple initialization if (window._vendorOrdersInitialized) { vendorOrdersLog.warn('Already initialized, skipping'); return; } window._vendorOrdersInitialized = true; // Load platform settings for rows per page if (window.PlatformSettings) { this.pagination.per_page = await window.PlatformSettings.getRowsPerPage(); } try { await this.loadOrders(); } catch (error) { vendorOrdersLog.error('Init failed:', error); this.error = 'Failed to initialize orders page'; } vendorOrdersLog.info('Orders initialization complete'); }, /** * Load orders with filtering and pagination */ async loadOrders() { this.loading = true; this.error = ''; try { const params = new URLSearchParams({ skip: (this.pagination.page - 1) * this.pagination.per_page, limit: this.pagination.per_page }); // Add filters if (this.filters.search) { params.append('search', this.filters.search); } if (this.filters.status) { params.append('status', this.filters.status); } if (this.filters.date_from) { params.append('date_from', this.filters.date_from); } if (this.filters.date_to) { params.append('date_to', this.filters.date_to); } const response = await apiClient.get(`/vendor/${this.vendorCode}/orders?${params.toString()}`); this.orders = response.orders || []; this.pagination.total = response.total || 0; this.pagination.pages = Math.ceil(this.pagination.total / this.pagination.per_page); // Calculate stats this.calculateStats(); vendorOrdersLog.info('Loaded orders:', this.orders.length, 'of', this.pagination.total); } catch (error) { vendorOrdersLog.error('Failed to load orders:', error); this.error = error.message || 'Failed to load orders'; } finally { this.loading = false; } }, /** * Calculate order statistics */ calculateStats() { this.stats = { total: this.pagination.total, pending: this.orders.filter(o => o.status === 'pending').length, processing: this.orders.filter(o => o.status === 'processing').length, completed: this.orders.filter(o => ['completed', 'delivered'].includes(o.status)).length, cancelled: this.orders.filter(o => o.status === 'cancelled').length }; }, /** * Debounced search handler */ debouncedSearch() { clearTimeout(this.searchTimeout); this.searchTimeout = setTimeout(() => { this.pagination.page = 1; this.loadOrders(); }, 300); }, /** * Apply filter and reload */ applyFilter() { this.pagination.page = 1; this.loadOrders(); }, /** * Clear all filters */ clearFilters() { this.filters = { search: '', status: '', date_from: '', date_to: '' }; this.pagination.page = 1; this.loadOrders(); }, /** * View order details - navigates to detail page */ viewOrder(order) { window.location.href = `/vendor/${this.vendorCode}/orders/${order.id}`; }, /** * Open status change modal */ openStatusModal(order) { this.selectedOrder = order; this.newStatus = order.status; this.showStatusModal = true; }, /** * Update order status */ async updateStatus() { if (!this.selectedOrder || !this.newStatus) return; this.saving = true; try { await apiClient.put(`/vendor/${this.vendorCode}/orders/${this.selectedOrder.id}/status`, { status: this.newStatus }); Utils.showToast('Order status updated', 'success'); vendorOrdersLog.info('Updated order status:', this.selectedOrder.id, this.newStatus); this.showStatusModal = false; this.selectedOrder = null; await this.loadOrders(); } catch (error) { vendorOrdersLog.error('Failed to update status:', error); Utils.showToast(error.message || 'Failed to update status', 'error'); } finally { this.saving = false; } }, /** * Get status color class */ getStatusColor(status) { const statusObj = this.statuses.find(s => s.value === status); return statusObj ? statusObj.color : 'gray'; }, /** * Get status label */ getStatusLabel(status) { const statusObj = this.statuses.find(s => s.value === status); return statusObj ? statusObj.label : status; }, /** * Format price for display */ formatPrice(cents) { if (!cents && cents !== 0) return '-'; return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(cents / 100); }, /** * Format date for display */ formatDate(dateStr) { if (!dateStr) return '-'; return new Date(dateStr).toLocaleDateString('de-DE', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); }, /** * Pagination: Previous page */ previousPage() { if (this.pagination.page > 1) { this.pagination.page--; this.loadOrders(); } }, /** * Pagination: Next page */ nextPage() { if (this.pagination.page < this.totalPages) { this.pagination.page++; this.loadOrders(); } }, /** * Pagination: Go to specific page */ goToPage(pageNum) { if (pageNum !== '...' && pageNum >= 1 && pageNum <= this.totalPages) { this.pagination.page = pageNum; this.loadOrders(); } }, // ============================================================================ // BULK OPERATIONS // ============================================================================ /** * Toggle select all orders on current page */ toggleSelectAll() { if (this.allSelected) { this.selectedOrders = []; } else { this.selectedOrders = this.orders.map(o => o.id); } }, /** * Toggle selection of a single order */ toggleSelect(orderId) { const index = this.selectedOrders.indexOf(orderId); if (index === -1) { this.selectedOrders.push(orderId); } else { this.selectedOrders.splice(index, 1); } }, /** * Check if order is selected */ isSelected(orderId) { return this.selectedOrders.includes(orderId); }, /** * Clear all selections */ clearSelection() { this.selectedOrders = []; }, /** * Open bulk status change modal */ openBulkStatusModal() { if (this.selectedOrders.length === 0) return; this.bulkStatus = ''; this.showBulkStatusModal = true; }, /** * Execute bulk status update */ async bulkUpdateStatus() { if (this.selectedOrders.length === 0 || !this.bulkStatus) return; this.saving = true; try { let successCount = 0; for (const orderId of this.selectedOrders) { try { await apiClient.put(`/vendor/${this.vendorCode}/orders/${orderId}/status`, { status: this.bulkStatus }); successCount++; } catch (error) { vendorOrdersLog.warn(`Failed to update order ${orderId}:`, error); } } Utils.showToast(`${successCount} order(s) updated to ${this.getStatusLabel(this.bulkStatus)}`, 'success'); this.showBulkStatusModal = false; this.clearSelection(); await this.loadOrders(); } catch (error) { vendorOrdersLog.error('Bulk status update failed:', error); Utils.showToast(error.message || 'Failed to update orders', 'error'); } finally { this.saving = false; } }, /** * Export selected orders as CSV */ exportSelectedOrders() { if (this.selectedOrders.length === 0) return; const selectedOrderData = this.orders.filter(o => this.selectedOrders.includes(o.id)); // Build CSV content const headers = ['Order ID', 'Date', 'Customer', 'Status', 'Total']; const rows = selectedOrderData.map(o => [ o.order_number || o.id, this.formatDate(o.created_at), o.customer_name || o.customer_email || '-', this.getStatusLabel(o.status), this.formatPrice(o.total) ]); const csvContent = [ headers.join(','), ...rows.map(row => row.map(cell => `"${cell}"`).join(',')) ].join('\n'); // Download const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `orders_export_${new Date().toISOString().split('T')[0]}.csv`; link.click(); Utils.showToast(`Exported ${selectedOrderData.length} order(s)`, 'success'); } }; }