// Products page JavaScript let routes = {}; let csrfToken = ''; // Initialize on page load document.addEventListener('DOMContentLoaded', function() { if (window.productRoutes) { routes = window.productRoutes; csrfToken = document.querySelector('meta[name="csrf-token"]')?.content || ''; } // Attach event listeners to buttons const fixCategoryProductsBtn = document.getElementById('fixCategoryProductsBtn'); if (fixCategoryProductsBtn) { fixCategoryProductsBtn.addEventListener('click', fixCategoryProducts); } }); function startProductMigration(dryRun) { const button = dryRun ? document.getElementById('dryRunProductMigrationBtn') : document.getElementById('startProductMigrationBtn'); const otherButton = dryRun ? document.getElementById('startProductMigrationBtn') : document.getElementById('dryRunProductMigrationBtn'); const originalText = button.textContent; button.disabled = true; otherButton.disabled = true; button.textContent = dryRun ? 'Running Dry Run...' : 'Migrating...'; button.style.cursor = 'not-allowed'; const logContent = document.getElementById('productMigrationLogContent'); logContent.innerHTML = '
' + (dryRun ? 'Running dry run...' : 'Starting migration...') + '
'; fetch(routes.migrateProducts, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken }, body: JSON.stringify({ dry_run: dryRun }) }) .then(response => response.json()) .then(data => { button.disabled = false; otherButton.disabled = false; button.textContent = originalText; button.style.cursor = 'pointer'; if (data.success) { const successEntry = document.createElement('div'); successEntry.className = 'log-entry success'; successEntry.textContent = `✓ ${dryRun ? 'Dry run' : 'Migration'} completed! Added: ${data.added || 0}, Updated: ${data.updated || 0}, Errors: ${data.errors || 0}`; logContent.appendChild(successEntry); if (data.log && data.log.length > 0) { data.log.forEach(log => { const entry = document.createElement('div'); entry.className = 'log-entry ' + (log.includes('ERROR') ? 'error' : 'success'); entry.textContent = log; logContent.appendChild(entry); }); } if (data.missing_attributes && data.missing_attributes.length > 0) { const missingSection = document.getElementById('missingAttributesSection'); const tbody = document.getElementById('missingAttributesTableBody'); tbody.innerHTML = ''; data.missing_attributes.forEach(attr => { const row = document.createElement('tr'); row.innerHTML = ` ${attr.attribute_code} ${attr.frontend_label || 'N/A'} ${attr.backend_type || 'N/A'} `; tbody.appendChild(row); }); missingSection.style.display = 'block'; } const logContainer = document.getElementById('productMigrationLogContainer'); logContainer.scrollTop = logContainer.scrollHeight; } else { const errorEntry = document.createElement('div'); errorEntry.className = 'log-entry error'; errorEntry.textContent = '✗ ' + (dryRun ? 'Dry run' : 'Migration') + ' failed: ' + (data.message || 'Unknown error'); logContent.appendChild(errorEntry); } }) .catch(error => { button.disabled = false; otherButton.disabled = false; button.textContent = originalText; button.style.cursor = 'pointer'; const errorEntry = document.createElement('div'); errorEntry.className = 'log-entry error'; errorEntry.textContent = '✗ Error: ' + error.message; logContent.appendChild(errorEntry); }); } function deleteM2Product(productId, productName, productSku) { if (!confirm(`Are you sure you want to delete product "${productName}" (SKU: ${productSku})?`)) { return; } const url = routes.deleteProduct.replace(':id', productId); fetch(url, { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken } }) .then(response => response.json()) .then(data => { if (data.success) { alert('Product deleted successfully!'); location.reload(); } else { alert('Error: ' + (data.message || 'Failed to delete product')); } }) .catch(error => { alert('Error: ' + error.message); }); } function deleteProductsAboveM1Max() { if (!confirm('Are you sure you want to delete all Magento 2 products with entity_id greater than the maximum Magento 1 product ID? This action cannot be undone.')) { return; } const button = document.getElementById('deleteProductsAboveM1MaxBtn'); const originalText = button.textContent; button.disabled = true; button.textContent = 'Deleting...'; fetch(routes.deleteProductsAboveM1Max, { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken } }) .then(response => response.json()) .then(data => { button.disabled = false; button.textContent = originalText; if (data.success) { alert(`Successfully deleted ${data.deleted || 0} products.`); location.reload(); } else { alert('Error: ' + (data.message || 'Failed to delete products')); } }) .catch(error => { button.disabled = false; button.textContent = originalText; alert('Error: ' + error.message); }); } function syncProductCategories() { if (!confirm('Are you sure you want to sync product category assignments from Magento 1 to Magento 2?')) { return; } const button = document.getElementById('syncProductCategoriesBtn'); const originalText = button.textContent; button.disabled = true; button.textContent = 'Syncing...'; fetch(routes.syncProductCategories, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken } }) .then(response => response.json()) .then(data => { button.disabled = false; button.textContent = originalText; if (data.success) { alert(`Sync completed! Updated: ${data.updated || 0}, Skipped: ${data.skipped || 0}, Errors: ${data.errors || 0}`); } else { alert('Error: ' + (data.message || 'Sync failed')); } }) .catch(error => { button.disabled = false; button.textContent = originalText; alert('Error: ' + error.message); }); } function fixCategoryProducts() { if (!confirm('Are you sure you want to fix category products? This will add missing products to the catalog_category_product table based on their category_ids attribute in Magento 2.')) { return; } const button = document.getElementById('fixCategoryProductsBtn'); const logContainer = document.getElementById('fixCategoryProductsLogContainer'); const logContent = document.getElementById('fixCategoryProductsLogContent'); if (!button) { console.error('fixCategoryProductsBtn not found'); return; } if (!routes.fixCategoryProducts) { console.error('routes.fixCategoryProducts not found', routes); alert('Error: Route not configured. Please refresh the page.'); return; } const originalText = button.textContent; button.disabled = true; button.textContent = 'Fixing...'; logContainer.style.display = 'block'; logContent.innerHTML = '
Starting fix process...
'; fetch(routes.fixCategoryProducts, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken } }) .then(response => response.json()) .then(data => { button.disabled = false; button.textContent = originalText; if (data.success) { const successEntry = document.createElement('div'); successEntry.className = 'log-entry success'; successEntry.textContent = `✓ Fix completed! Added: ${data.added || 0}, Skipped: ${data.skipped || 0}, Errors: ${data.errors || 0}`; logContent.appendChild(successEntry); if (data.log && data.log.length > 0) { data.log.forEach(log => { const entry = document.createElement('div'); entry.className = 'log-entry ' + (log.includes('ERROR') ? 'error' : (log.includes('ADDED') ? 'success' : 'info')); entry.textContent = log; logContent.appendChild(entry); }); } logContent.scrollTop = logContent.scrollHeight; } else { const errorEntry = document.createElement('div'); errorEntry.className = 'log-entry error'; errorEntry.textContent = '✗ Fix failed: ' + (data.message || 'Unknown error'); logContent.appendChild(errorEntry); } }) .catch(error => { button.disabled = false; button.textContent = originalText; const errorEntry = document.createElement('div'); errorEntry.className = 'log-entry error'; errorEntry.textContent = '✗ Error: ' + error.message; logContent.appendChild(errorEntry); }); } function loadM1CategoryTreeWithProducts() { const container = document.getElementById('m1-category-products-tree-container'); if (!container) return; container.innerHTML = '
Loading categories and products...
'; fetch(routes.magento1CategoryTreeWithProducts) .then(response => response.json()) .then(data => { if (data.success) { container.innerHTML = ''; if (data.tree && data.tree.length > 0) { renderCategoryTreeWithProducts(container, data.tree); } else { container.innerHTML = '
No categories found
'; } } else { container.innerHTML = `
Error: ${data.message || 'Failed to load categories'}
`; } }) .catch(error => { container.innerHTML = `
Error: ${error.message}
`; }); } function loadM2CategoryTreeWithProducts() { const container = document.getElementById('m2-category-products-tree-container'); if (!container) return; container.innerHTML = '
Loading categories and products...
'; fetch(routes.magento2CategoryTreeWithProducts) .then(response => response.json()) .then(data => { if (data.success) { container.innerHTML = ''; if (data.tree && data.tree.length > 0) { renderCategoryTreeWithProducts(container, data.tree); } else { container.innerHTML = '
No categories found
'; } } else { container.innerHTML = `
Error: ${data.message || 'Failed to load categories'}
`; } }) .catch(error => { container.innerHTML = `
Error: ${error.message}
`; }); } function renderCategoryTreeWithProducts(container, tree) { tree.forEach(node => { const nodeElement = createCategoryWithProductsNode(node); container.appendChild(nodeElement); }); } function toggleNode(toggleElement) { const nodeItem = toggleElement.parentElement; const nodeDiv = nodeItem.parentElement; const childrenDivs = nodeDiv.querySelectorAll('.tree-children'); if (childrenDivs.length > 0) { let isExpanded = false; childrenDivs.forEach(div => { if (div.style.display !== 'none') { isExpanded = true; } }); if (isExpanded) { childrenDivs.forEach(div => { div.style.display = 'none'; }); toggleElement.classList.remove('expanded'); toggleElement.classList.add('collapsed'); } else { childrenDivs.forEach(div => { div.style.display = 'block'; }); toggleElement.classList.remove('collapsed'); toggleElement.classList.add('expanded'); } } } function createCategoryWithProductsNode(node) { const nodeDiv = document.createElement('div'); nodeDiv.className = 'tree-node'; const hasChildren = node.children && node.children.length > 0; const hasProducts = node.products && node.products.length > 0; const productCount = node.product_count !== undefined ? node.product_count : (node.products ? node.products.length : 0); const itemDiv = document.createElement('div'); itemDiv.className = 'tree-node-item'; const toggle = document.createElement('span'); toggle.className = (hasChildren || hasProducts) ? 'tree-toggle collapsed' : 'tree-toggle leaf'; if (hasChildren || hasProducts) { toggle.onclick = function(e) { e.stopPropagation(); toggleNode(this); }; } const label = document.createElement('div'); label.className = 'tree-label'; const labelText = document.createElement('span'); labelText.className = 'tree-label-text'; labelText.textContent = `[${node.id}] ${node.name || 'Unnamed Category'} (${productCount} products)`; const badge = document.createElement('span'); badge.className = `tree-badge ${node.is_active ? 'active' : 'inactive'}`; badge.textContent = node.is_active ? 'Active' : 'Inactive'; label.appendChild(labelText); label.appendChild(badge); itemDiv.appendChild(toggle); itemDiv.appendChild(label); nodeDiv.appendChild(itemDiv); // Add products section if products exist if (hasProducts) { const productsDiv = document.createElement('div'); productsDiv.className = 'tree-children'; productsDiv.style.display = 'none'; const productsHeader = document.createElement('div'); productsHeader.style.cssText = 'padding: 8px 12px; font-weight: 600; color: #667eea; background: #f0f0f0; border-radius: 4px; margin: 5px 0;'; productsHeader.textContent = `Products (${node.products.length}):`; productsDiv.appendChild(productsHeader); node.products.forEach(product => { const productDiv = document.createElement('div'); productDiv.style.cssText = 'padding: 6px 12px 6px 30px; font-size: 0.9em; color: #666; border-left: 2px solid #e0e0e0; margin-left: 20px;'; productDiv.textContent = `ID: ${product.id || product.product_id || 'N/A'} | SKU: ${product.sku || 'N/A'} | Name: ${product.name || 'Unnamed Product'}`; productsDiv.appendChild(productDiv); }); nodeDiv.appendChild(productsDiv); } // Add children if (hasChildren) { const childrenDiv = document.createElement('div'); childrenDiv.className = 'tree-children'; childrenDiv.style.display = 'none'; node.children.forEach(child => { childrenDiv.appendChild(createCategoryWithProductsNode(child)); }); nodeDiv.appendChild(childrenDiv); } return nodeDiv; } // Make functions available globally window.startProductMigration = startProductMigration; window.deleteM2Product = deleteM2Product; window.deleteProductsAboveM1Max = deleteProductsAboveM1Max; window.syncProductCategories = syncProductCategories; window.fixCategoryProducts = fixCategoryProducts; window.loadM1CategoryTreeWithProducts = loadM1CategoryTreeWithProducts; window.loadM2CategoryTreeWithProducts = loadM2CategoryTreeWithProducts;