// 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) { // Check if routes are available if (!routes || !routes.migrateProducts) { console.error('Product routes not available', routes); alert('Error: Routes not initialized. Please refresh the page.'); return; } const button = dryRun ? document.getElementById('dryRunProductMigrationBtn') : document.getElementById('startProductMigrationBtn'); const otherButton = dryRun ? document.getElementById('startProductMigrationBtn') : document.getElementById('dryRunProductMigrationBtn'); if (!button) { console.error('Button not found'); alert('Error: Button not found. Please refresh the page.'); return; } const originalText = button.textContent; button.disabled = true; if (otherButton) { otherButton.disabled = true; } button.textContent = dryRun ? 'Running Dry Run...' : 'Migrating...'; button.style.cursor = 'not-allowed'; const logContent = document.getElementById('productMigrationLogContent'); const progressContainer = document.getElementById('migrationProgressContainer'); if (!logContent) { console.error('Log content element not found'); button.disabled = false; if (otherButton) { otherButton.disabled = false; } button.textContent = originalText; return; } // Show progress bar if (progressContainer && !dryRun) { progressContainer.style.display = 'block'; updateProgressBar(0, 0, 0, 0, 0, 'Starting migration...', ''); } logContent.innerHTML = '
' + (dryRun ? 'Running dry run...' : 'Starting migration...') + '
'; // Generate progress key for tracking (only for non-dry-run) let progressKey = null; let progressInterval = null; if (!dryRun) { progressKey = 'product_migration_progress_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); // Start polling immediately progressInterval = pollMigrationProgress(progressKey, button, otherButton, originalText, logContent); } fetch(routes.migrateProducts, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken }, body: JSON.stringify({ dry_run: dryRun, progress_key: progressKey }) }) .then(response => response.json()) .then(data => { // Stop polling when migration completes if (progressInterval) { clearInterval(progressInterval); } // Handle final result handleMigrationComplete(data, button, otherButton, originalText, logContent, dryRun); }) .catch(error => { // Stop polling on error if (progressInterval) { clearInterval(progressInterval); } button.disabled = false; if (otherButton) { otherButton.disabled = false; } button.textContent = originalText; button.style.cursor = 'pointer'; const progressContainer = document.getElementById('migrationProgressContainer'); if (progressContainer) { progressContainer.style.display = 'none'; } const errorEntry = document.createElement('div'); errorEntry.className = 'log-entry error'; errorEntry.textContent = '✗ Error: ' + error.message; logContent.appendChild(errorEntry); }); } function pollMigrationProgress(progressKey, button, otherButton, originalText, logContent) { return setInterval(() => { if (!routes.migrationProgress) { console.error('Migration progress route not available'); return; } fetch(routes.migrationProgress + '?progress_key=' + encodeURIComponent(progressKey), { method: 'GET', headers: { 'X-CSRF-TOKEN': csrfToken } }) .then(response => response.json()) .then(data => { if (data.success && data.progress) { const progress = data.progress; const percentage = progress.total > 0 ? Math.round((progress.current / progress.total) * 100) : 0; updateProgressBar( percentage, progress.current, progress.total, progress.added, progress.updated, progress.errors, progress.status, progress.current_sku ); } else if (data.success === false && data.message === 'Progress not found') { // Progress not found yet, migration might not have started // This is okay, just continue polling } }) .catch(error => { console.error('Error polling progress:', error); }); }, 500); // Poll every 500ms for smoother updates } function updateProgressBar(percentage, current, total, added, updated, errors, status, currentSku) { const progressBar = document.getElementById('progressBar'); const progressPercentage = document.getElementById('progressPercentage'); const progressStatus = document.getElementById('progressStatus'); const currentProduct = document.getElementById('currentProduct'); const progressAdded = document.getElementById('progressAdded'); const progressUpdated = document.getElementById('progressUpdated'); const progressErrors = document.getElementById('progressErrors'); if (progressBar) { progressBar.style.width = percentage + '%'; } if (progressPercentage) { progressPercentage.textContent = percentage + '%'; } if (progressStatus) { const statusText = status === 'running' ? 'Migrating...' : status === 'completed' ? 'Migration completed!' : status === 'failed' ? 'Migration failed!' : 'Starting...'; progressStatus.textContent = statusText; } if (currentProduct) { currentProduct.textContent = currentSku ? `Current: ${currentSku}` : `Processing ${current} of ${total}`; } if (progressAdded) { progressAdded.textContent = added; } if (progressUpdated) { progressUpdated.textContent = updated; } if (progressErrors) { progressErrors.textContent = errors; } } function handleMigrationComplete(data, button, otherButton, originalText, logContent, dryRun) { button.disabled = false; if (otherButton) { otherButton.disabled = false; } button.textContent = originalText; button.style.cursor = 'pointer'; const progressContainer = document.getElementById('migrationProgressContainer'); if (progressContainer) { if (data.success) { updateProgressBar(100, data.added + data.updated, data.added + data.updated, data.added, data.updated, data.errors, 'completed', ''); } } if (data.success) { // Clear log content and show only errors logContent.innerHTML = ''; if (data.log && data.log.length > 0) { // Filter to show only error logs (case-insensitive check) const errorLogs = data.log.filter(log => { const logUpper = log.toUpperCase(); return logUpper.includes('ERROR'); }); if (errorLogs.length > 0) { // Show summary with error count const summaryEntry = document.createElement('div'); summaryEntry.className = 'log-entry'; summaryEntry.style.cssText = 'font-weight: 600; margin-bottom: 10px; color: #dc3545;'; summaryEntry.textContent = `⚠️ Migration completed with ${errorLogs.length} error(s). Added: ${data.added || 0}, Updated: ${data.updated || 0}`; logContent.appendChild(summaryEntry); // Show each error errorLogs.forEach(log => { const entry = document.createElement('div'); entry.className = 'log-entry error'; entry.textContent = log; logContent.appendChild(entry); }); } else { // No errors found const noErrorsEntry = document.createElement('div'); noErrorsEntry.className = 'log-entry'; noErrorsEntry.style.cssText = 'color: #28a745; font-weight: 600;'; noErrorsEntry.textContent = `✓ ${dryRun ? 'Dry run' : 'Migration'} completed successfully! Added: ${data.added || 0}, Updated: ${data.updated || 0}, Errors: 0`; logContent.appendChild(noErrorsEntry); } } else { // No logs at all const noLogsEntry = document.createElement('div'); noLogsEntry.className = 'log-entry'; noLogsEntry.style.cssText = 'color: #28a745; font-weight: 600;'; noLogsEntry.textContent = `✓ ${dryRun ? 'Dry run' : 'Migration'} completed! Added: ${data.added || 0}, Updated: ${data.updated || 0}, Errors: ${data.errors || 0}`; logContent.appendChild(noLogsEntry); } 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); } } 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; } function startProductOptionsMigration(dryRun) { // Check if routes are available if (!routes || !routes.migrateProductOptions) { console.error('Product options routes not available', routes); alert('Error: Routes not initialized. Please refresh the page.'); return; } const button = dryRun ? document.getElementById('dryRunProductOptionsMigrationBtn') : document.getElementById('startProductOptionsMigrationBtn'); const otherButton = dryRun ? document.getElementById('startProductOptionsMigrationBtn') : document.getElementById('dryRunProductOptionsMigrationBtn'); const logContainer = document.getElementById('productOptionsMigrationLogContainer'); const logContent = document.getElementById('productOptionsMigrationLogContent'); if (!button) { console.error('Button not found'); alert('Error: Button not found. Please refresh the page.'); return; } const originalText = button.textContent; button.disabled = true; if (otherButton) { otherButton.disabled = true; } button.textContent = dryRun ? 'Running Dry Run...' : 'Migrating...'; button.style.cursor = 'not-allowed'; logContainer.style.display = 'block'; logContent.innerHTML = '
' + (dryRun ? 'Running dry run...' : 'Starting migration...') + '
'; fetch(routes.migrateProductOptions, { 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; if (otherButton) { 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! Migrated: ${data.migrated || 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('WARNING') ? 'warning' : (log.includes('INFO') ? 'info' : 'success'))); entry.textContent = log; logContent.appendChild(entry); }); } logContent.scrollTop = logContent.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); if (data.log && data.log.length > 0) { data.log.forEach(log => { const entry = document.createElement('div'); entry.className = 'log-entry ' + (log.includes('ERROR') ? 'error' : 'warning'); entry.textContent = log; logContent.appendChild(entry); }); } } }) .catch(error => { button.disabled = false; if (otherButton) { 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 compareProductBySku() { const skuInput = document.getElementById('productSkuInput'); const compareBtn = document.getElementById('compareProductBtn'); const comparisonSection = document.getElementById('productComparisonSection'); const comparisonMessage = document.getElementById('productComparisonMessage'); if (!skuInput || !compareBtn || !comparisonSection) { console.error('Required elements not found'); alert('Error: Required elements not found. Please refresh the page.'); return; } const sku = skuInput.value.trim(); if (!sku) { alert('Please enter a product SKU'); return; } if (!routes || !routes.compareProductBySku) { console.error('Route not available', routes); alert('Error: Route not configured. Please refresh the page.'); return; } const originalText = compareBtn.textContent; compareBtn.disabled = true; compareBtn.textContent = 'Comparing...'; comparisonSection.style.display = 'none'; fetch(routes.compareProductBySku, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken }, body: JSON.stringify({ sku: sku }) }) .then(response => response.json()) .then(data => { compareBtn.disabled = false; compareBtn.textContent = originalText; comparisonSection.style.display = 'block'; const tablesContainer = document.getElementById('productComparisonTablesContainer'); if (!tablesContainer) { console.error('Tables container not found'); return; } // Clear previous results tablesContainer.innerHTML = ''; if (data.success) { // Display message comparisonMessage.textContent = data.message || 'Comparison completed'; comparisonMessage.style.background = '#d4edda'; comparisonMessage.style.color = '#155724'; comparisonMessage.style.border = '1px solid #c3e6cb'; if (data.m1_product_id) { const infoText = document.createElement('div'); infoText.style.marginTop = '5px'; infoText.style.fontSize = '0.9em'; infoText.textContent = `M1 Product ID: ${data.m1_product_id}${data.m2_product_id ? ` | M2 Product ID: ${data.m2_product_id}` : ''}`; comparisonMessage.appendChild(infoText); } // Display tables for each backend type if (data.changes_by_table) { const backendTypes = ['varchar', 'int', 'text', 'decimal', 'datetime']; let hasAnyChanges = false; backendTypes.forEach(backendType => { const tableData = data.changes_by_table[backendType]; if (!tableData) return; const changes = tableData.changes || []; if (changes.length > 0) { hasAnyChanges = true; } // Create table container const tableWrapper = document.createElement('div'); tableWrapper.style.cssText = 'background: white; border: 1px solid #ddd; border-radius: 6px; padding: 20px; margin-bottom: 20px; max-height: 600px; overflow-y: auto;'; // Table title const tableTitle = document.createElement('h4'); tableTitle.style.cssText = 'margin: 0 0 15px 0; color: #333; font-size: 1.1em;'; tableTitle.textContent = `Table: ${tableData.table_name}`; tableWrapper.appendChild(tableTitle); // Table info const tableInfo = document.createElement('div'); tableInfo.style.cssText = 'margin-bottom: 15px; padding: 8px; background: #f8f9fa; border-radius: 4px; font-size: 0.9em; color: #666;'; tableInfo.innerHTML = ` M1 Table: ${tableData.m1_table}
M2 Table: ${tableData.m2_table}
Changes: ${changes.length} `; tableWrapper.appendChild(tableInfo); // Create table const table = document.createElement('table'); table.className = 'products-table'; table.style.width = '100%'; // Table header const thead = document.createElement('thead'); thead.innerHTML = ` Attribute Code Attribute Label Store ID M1 Value (To Update) M2 Current Value Action `; table.appendChild(thead); // Table body const tbody = document.createElement('tbody'); if (changes.length > 0) { changes.forEach(change => { const row = document.createElement('tr'); // Determine row style based on action if (change.action === 'INSERT') { row.style.backgroundColor = '#d4edda'; } else if (change.action === 'UPDATE') { row.style.backgroundColor = '#fff3cd'; } else if (change.action === 'DELETE') { row.style.backgroundColor = '#f8d7da'; } // Format values for display const m1Value = change.m1_value !== null && change.m1_value !== undefined ? (change.m1_value.toString().length > 100 ? change.m1_value.toString().substring(0, 100) + '...' : change.m1_value.toString()) : '(empty)'; const m2Value = change.m2_value !== null && change.m2_value !== undefined ? (change.m2_value.toString().length > 100 ? change.m2_value.toString().substring(0, 100) + '...' : change.m2_value.toString()) : '(empty)'; // Determine action badge color let actionBadgeClass = ''; let actionText = change.action; if (change.action === 'INSERT') { actionBadgeClass = 'style="background-color: #28a745; color: white; padding: 2px 8px; border-radius: 3px; font-size: 0.85em;"'; } else if (change.action === 'UPDATE') { actionBadgeClass = 'style="background-color: #ffc107; color: #000; padding: 2px 8px; border-radius: 3px; font-size: 0.85em;"'; } else if (change.action === 'DELETE') { actionBadgeClass = 'style="background-color: #dc3545; color: white; padding: 2px 8px; border-radius: 3px; font-size: 0.85em;"'; } row.innerHTML = ` ${change.attribute_code || 'N/A'} ${change.attribute_label || 'N/A'} ${change.store_id || 0} ${m1Value} ${m2Value} ${actionText} `; tbody.appendChild(row); }); } else { const row = document.createElement('tr'); row.innerHTML = ` No changes in this table. Values are identical in M1 and M2. `; tbody.appendChild(row); } table.appendChild(tbody); tableWrapper.appendChild(table); tablesContainer.appendChild(tableWrapper); }); if (!hasAnyChanges) { const noChangesMsg = document.createElement('div'); noChangesMsg.style.cssText = 'text-align: center; padding: 40px; color: #666; background: white; border: 1px solid #ddd; border-radius: 6px;'; noChangesMsg.textContent = 'No differences found. The product values are identical in M1 and M2 across all tables.'; tablesContainer.appendChild(noChangesMsg); } } else { const noDataMsg = document.createElement('div'); noDataMsg.style.cssText = 'text-align: center; padding: 40px; color: #666; background: white; border: 1px solid #ddd; border-radius: 6px;'; noDataMsg.textContent = 'No comparison data available.'; tablesContainer.appendChild(noDataMsg); } } else { // Error case comparisonMessage.textContent = data.message || 'Comparison failed'; comparisonMessage.style.background = '#f8d7da'; comparisonMessage.style.color = '#721c24'; comparisonMessage.style.border = '1px solid #f5c6cb'; const errorMsg = document.createElement('div'); errorMsg.style.cssText = 'text-align: center; padding: 40px; color: #dc3545; background: white; border: 1px solid #ddd; border-radius: 6px;'; errorMsg.textContent = data.message || 'Failed to compare product'; tablesContainer.appendChild(errorMsg); } }) .catch(error => { compareBtn.disabled = false; compareBtn.textContent = originalText; comparisonSection.style.display = 'block'; const tablesContainer = document.getElementById('productComparisonTablesContainer'); if (tablesContainer) { tablesContainer.innerHTML = ''; } comparisonMessage.textContent = 'Error: ' + error.message; comparisonMessage.style.background = '#f8d7da'; comparisonMessage.style.color = '#721c24'; comparisonMessage.style.border = '1px solid #f5c6cb'; if (tablesContainer) { const errorMsg = document.createElement('div'); errorMsg.style.cssText = 'text-align: center; padding: 40px; color: #dc3545; background: white; border: 1px solid #ddd; border-radius: 6px;'; errorMsg.textContent = 'Error: ' + error.message; tablesContainer.appendChild(errorMsg); } }); } // Add Enter key support for SKU input document.addEventListener('DOMContentLoaded', function() { const skuInput = document.getElementById('productSkuInput'); if (skuInput) { skuInput.addEventListener('keypress', function(e) { if (e.key === 'Enter') { compareProductBySku(); } }); } }); // Make functions available globally window.startProductMigration = startProductMigration; window.startProductOptionsMigration = startProductOptionsMigration; window.deleteM2Product = deleteM2Product; window.deleteProductsAboveM1Max = deleteProductsAboveM1Max; window.syncProductCategories = syncProductCategories; window.fixCategoryProducts = fixCategoryProducts; window.loadM1CategoryTreeWithProducts = loadM1CategoryTreeWithProducts; window.loadM2CategoryTreeWithProducts = loadM2CategoryTreeWithProducts; window.compareProductBySku = compareProductBySku;