// static/admin/js/marketplace-product-detail.js /** * Admin marketplace product detail page logic * View and manage individual marketplace products */ const adminMarketplaceProductDetailLog = window.LogConfig.loggers.adminMarketplaceProductDetail || window.LogConfig.createLogger('adminMarketplaceProductDetail', false); adminMarketplaceProductDetailLog.info('Loading...'); function adminMarketplaceProductDetail() { adminMarketplaceProductDetailLog.info('adminMarketplaceProductDetail() called'); // Extract product ID from URL const pathParts = window.location.pathname.split('/'); const productId = parseInt(pathParts[pathParts.length - 1]); return { // Inherit base layout state ...data(), // Set page identifier currentPage: 'marketplace-products', // Product ID from URL productId: productId, // Loading states loading: true, error: '', // Product data product: null, // Copy to vendor modal state showCopyModal: false, copying: false, copyForm: { vendor_id: '', skip_existing: true }, targetVendors: [], async init() { adminMarketplaceProductDetailLog.info('Marketplace Product Detail init() called, ID:', this.productId); // Guard against multiple initialization if (window._adminMarketplaceProductDetailInitialized) { adminMarketplaceProductDetailLog.warn('Already initialized, skipping'); return; } window._adminMarketplaceProductDetailInitialized = true; // Load data in parallel await Promise.all([ this.loadProduct(), this.loadTargetVendors() ]); adminMarketplaceProductDetailLog.info('Marketplace Product Detail initialization complete'); }, /** * Load product details */ async loadProduct() { this.loading = true; this.error = ''; try { const response = await apiClient.get(`/admin/products/${this.productId}`); this.product = response; adminMarketplaceProductDetailLog.info('Loaded product:', this.product.marketplace_product_id); } catch (error) { adminMarketplaceProductDetailLog.error('Failed to load product:', error); this.error = error.message || 'Failed to load product details'; } finally { this.loading = false; } }, /** * Load target vendors for copy functionality */ async loadTargetVendors() { try { const response = await apiClient.get('/admin/vendors?is_active=true&limit=500'); this.targetVendors = response.vendors || []; adminMarketplaceProductDetailLog.info('Loaded target vendors:', this.targetVendors.length); } catch (error) { adminMarketplaceProductDetailLog.error('Failed to load target vendors:', error); } }, /** * Open copy modal */ openCopyModal() { this.copyForm.vendor_id = ''; this.showCopyModal = true; adminMarketplaceProductDetailLog.info('Opening copy modal for product:', this.productId); }, /** * Execute copy to vendor catalog */ async executeCopyToVendor() { if (!this.copyForm.vendor_id) { this.error = 'Please select a target vendor'; return; } this.copying = true; try { const response = await apiClient.post('/admin/products/copy-to-vendor', { marketplace_product_ids: [this.productId], vendor_id: parseInt(this.copyForm.vendor_id), skip_existing: this.copyForm.skip_existing }); adminMarketplaceProductDetailLog.info('Copy result:', response); // Show success message const copied = response.copied || 0; const skipped = response.skipped || 0; const failed = response.failed || 0; let message; if (copied > 0) { message = 'Product successfully copied to vendor catalog.'; } else if (skipped > 0) { message = 'Product already exists in the vendor catalog.'; } else { message = 'Failed to copy product.'; } // Close modal this.showCopyModal = false; // Show notification Utils.showToast(message, copied > 0 ? 'success' : 'warning'); } catch (error) { adminMarketplaceProductDetailLog.error('Failed to copy product:', error); this.error = error.message || 'Failed to copy product to vendor catalog'; } finally { this.copying = false; } }, /** * Format price for display */ formatPrice(price, currency = 'EUR') { if (price === null || price === undefined) return '-'; return new Intl.NumberFormat('en-US', { style: 'currency', currency: currency || 'EUR' }).format(price); }, /** * Format date for display */ formatDate(dateString) { if (!dateString) return '-'; try { const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } catch (e) { return dateString; } }, /** * Get full language name from ISO code (native names for Luxembourg languages) */ getLanguageName(code) { const languages = { 'en': 'English', 'de': 'Deutsch', 'fr': 'Français', 'lb': 'Lëtzebuergesch', 'es': 'Español', 'it': 'Italiano', 'nl': 'Nederlands', 'pt': 'Português', 'pl': 'Polski', 'cs': 'Čeština', 'da': 'Dansk', 'sv': 'Svenska', 'fi': 'Suomi', 'no': 'Norsk', 'hu': 'Hungarian', 'ro': 'Romanian', 'bg': 'Bulgarian', 'el': 'Greek', 'sk': 'Slovak', 'sl': 'Slovenian', 'hr': 'Croatian', 'lt': 'Lithuanian', 'lv': 'Latvian', 'et': 'Estonian' }; return languages[code?.toLowerCase()] || ''; }, /** * Copy text to clipboard */ async copyToClipboard(text) { if (!text) return; try { await navigator.clipboard.writeText(text); Utils.showToast('Copied to clipboard', 'success'); } catch (err) { adminMarketplaceProductDetailLog.error('Failed to copy to clipboard:', err); Utils.showToast('Failed to copy to clipboard', 'error'); } } }; }