added category listing

This commit is contained in:
Chris Rosenau 2025-11-08 12:03:55 -07:00
parent f8241e4305
commit 44c7c7354a
4 changed files with 531 additions and 0 deletions

View File

@ -68,6 +68,50 @@ public function getMagento1Categories()
]); ]);
} }
/**
* Get Magento 1 category tree
*/
public function getMagento1CategoryTree()
{
try {
$categories = $this->migrationService->getMagento1Categories();
$tree = $this->migrationService->buildCategoryTreeHierarchy($categories);
return response()->json([
'success' => true,
'tree' => $tree,
]);
} catch (\Exception $e) {
Log::error('Error fetching M1 category tree: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Failed to fetch category tree: ' . $e->getMessage(),
], 500);
}
}
/**
* Get Magento 2 category tree
*/
public function getMagento2CategoryTree()
{
try {
$categories = $this->migrationService->getMagento2Categories();
$tree = $this->migrationService->buildCategoryTreeHierarchy($categories);
return response()->json([
'success' => true,
'tree' => $tree,
]);
} catch (\Exception $e) {
Log::error('Error fetching M2 category tree: ' . $e->getMessage());
return response()->json([
'success' => false,
'message' => 'Failed to fetch category tree: ' . $e->getMessage(),
], 500);
}
}
/** /**
* Execute the migration * Execute the migration
*/ */

View File

@ -152,6 +152,156 @@ public function getMagento1Categories($storeId = null)
} }
} }
/**
* Get all categories from Magento 2 for a specific store
*/
public function getMagento2Categories($storeId = null)
{
try {
// Get entity type ID
$entityTypeId = DB::connection($this->magento2Connection)
->table($this->magento2Prefix . 'eav_entity_type')
->where('entity_type_code', 'catalog_category')
->value('entity_type_id');
if (!$entityTypeId) {
return collect([]);
}
// Get attribute IDs
$nameAttributeId = DB::connection($this->magento2Connection)
->table($this->magento2Prefix . 'eav_attribute')
->where('entity_type_id', $entityTypeId)
->where('attribute_code', 'name')
->value('attribute_id');
$isActiveAttributeId = DB::connection($this->magento2Connection)
->table($this->magento2Prefix . 'eav_attribute')
->where('entity_type_id', $entityTypeId)
->where('attribute_code', 'is_active')
->value('attribute_id');
$urlKeyAttributeId = DB::connection($this->magento2Connection)
->table($this->magento2Prefix . 'eav_attribute')
->where('entity_type_id', $entityTypeId)
->where('attribute_code', 'url_key')
->value('attribute_id');
$targetStoreId = $storeId ?? 0;
// Get base category data
$categories = DB::connection($this->magento2Connection)
->table($this->magento2Prefix . 'catalog_category_entity')
->select('entity_id', 'parent_id', 'level', 'path', 'position')
->orderBy('level')
->orderBy('position')
->get();
// Get attribute values
$nameValues = [];
$isActiveValues = [];
$urlKeyValues = [];
if ($nameAttributeId) {
$nameValues = DB::connection($this->magento2Connection)
->table($this->magento2Prefix . 'catalog_category_entity_varchar')
->where('attribute_id', $nameAttributeId)
->where('store_id', $targetStoreId)
->pluck('value', 'entity_id')
->toArray();
}
if ($isActiveAttributeId) {
$isActiveValues = DB::connection($this->magento2Connection)
->table($this->magento2Prefix . 'catalog_category_entity_int')
->where('attribute_id', $isActiveAttributeId)
->where('store_id', $targetStoreId)
->pluck('value', 'entity_id')
->toArray();
}
if ($urlKeyAttributeId) {
$urlKeyValues = DB::connection($this->magento2Connection)
->table($this->magento2Prefix . 'catalog_category_entity_varchar')
->where('attribute_id', $urlKeyAttributeId)
->where('store_id', $targetStoreId)
->pluck('value', 'entity_id')
->toArray();
}
// Combine data
return $categories->map(function($category) use ($nameValues, $isActiveValues, $urlKeyValues) {
$category->name = $nameValues[$category->entity_id] ?? null;
$category->is_active = $isActiveValues[$category->entity_id] ?? null;
$category->url_key = $urlKeyValues[$category->entity_id] ?? null;
return $category;
});
} catch (Exception $e) {
Log::error('Error fetching Magento 2 categories: ' . $e->getMessage());
return collect([]);
}
}
/**
* Build hierarchical category tree structure
*/
public function buildCategoryTreeHierarchy($categories)
{
$categoryMap = [];
$rootCategories = [];
// First pass: create a map of all categories with children array
foreach ($categories as $category) {
$categoryMap[$category->entity_id] = [
'id' => $category->entity_id,
'name' => $category->name ?? 'Unnamed Category',
'parent_id' => $category->parent_id,
'level' => $category->level ?? 0,
'position' => $category->position ?? 0,
'is_active' => $category->is_active ?? 0,
'path' => $category->path ?? '',
'children' => []
];
}
// Second pass: build the tree structure by assigning children to parents
foreach ($categoryMap as $id => $category) {
$parentId = $category['parent_id'];
// Skip if parent is self (prevent circular references)
if ($parentId == $id) {
$rootCategories[] = &$categoryMap[$id];
continue;
}
// Check if this is a root category (parent is 0, 1, or doesn't exist in map)
if ($parentId == 0 || $parentId == 1 || !isset($categoryMap[$parentId])) {
$rootCategories[] = &$categoryMap[$id];
} else {
// Add as child of parent
$categoryMap[$parentId]['children'][] = &$categoryMap[$id];
}
}
// Sort function for categories
$sortCategories = function(&$categories) use (&$sortCategories) {
usort($categories, function($a, $b) {
return $a['position'] <=> $b['position'];
});
// Recursively sort children
foreach ($categories as &$category) {
if (!empty($category['children'])) {
$sortCategories($category['children']);
}
}
};
// Sort root categories and all children recursively
$sortCategories($rootCategories);
return $rootCategories;
}
/** /**
* Migrate categories from Magento 1 to Magento 2 * Migrate categories from Magento 1 to Magento 2
*/ */

View File

@ -251,6 +251,154 @@
color: #666; color: #666;
margin-top: 5px; margin-top: 5px;
} }
/* Tabs */
.tabs {
display: flex;
border-bottom: 2px solid #ddd;
margin-bottom: 20px;
background: #f8f9fa;
border-radius: 8px 8px 0 0;
}
.tab {
padding: 15px 30px;
cursor: pointer;
border: none;
background: transparent;
font-size: 1em;
font-weight: 600;
color: #666;
transition: all 0.3s;
border-bottom: 3px solid transparent;
margin-bottom: -2px;
}
.tab:hover {
background: #e9ecef;
color: #333;
}
.tab.active {
color: #667eea;
border-bottom-color: #667eea;
background: white;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
/* Tree View */
.tree-container {
background: white;
border: 1px solid #ddd;
border-radius: 6px;
padding: 20px;
max-height: 600px;
overflow-y: auto;
}
.tree-node {
margin: 5px 0;
}
.tree-node-item {
display: flex;
align-items: center;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
user-select: none;
transition: background 0.2s;
}
.tree-node-item:hover {
background: #f0f0f0;
}
.tree-toggle {
width: 20px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-right: 8px;
font-size: 12px;
color: #666;
cursor: pointer;
}
.tree-toggle.expanded::before {
content: '▼';
}
.tree-toggle.collapsed::before {
content: '▶';
}
.tree-toggle.leaf {
width: 20px;
margin-right: 8px;
}
.tree-label {
flex: 1;
display: flex;
align-items: center;
gap: 10px;
}
.tree-label-text {
font-weight: 500;
color: #333;
}
.tree-badge {
padding: 2px 8px;
border-radius: 12px;
font-size: 0.75em;
font-weight: 600;
}
.tree-badge.active {
background: #d4edda;
color: #155724;
}
.tree-badge.inactive {
background: #f8d7da;
color: #721c24;
}
.tree-children {
margin-left: 28px;
border-left: 2px solid #e0e0e0;
padding-left: 12px;
display: none;
}
.tree-children.expanded {
display: block;
}
.tree-loading {
text-align: center;
padding: 40px;
color: #666;
}
.tree-error {
background: #f8d7da;
color: #721c24;
padding: 15px;
border-radius: 6px;
margin: 10px 0;
}
</style> </style>
</head> </head>
<body> <body>
@ -261,6 +409,14 @@
</div> </div>
<div class="content"> <div class="content">
<!-- Tabs -->
<div class="tabs">
<button class="tab active" onclick="switchTab('migration', this)">Migration</button>
<button class="tab" onclick="switchTab('categories', this)">Category Trees</button>
</div>
<!-- Migration Tab -->
<div id="tab-migration" class="tab-content active">
<!-- Connection Status --> <!-- Connection Status -->
<div class="section"> <div class="section">
<h2>📡 Database Connections</h2> <h2>📡 Database Connections</h2>
@ -363,6 +519,33 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Category Trees Tab -->
<div id="tab-categories" class="tab-content">
<div class="section">
<h2>🌳 Category Trees</h2>
<p>View category hierarchies from Magento 1 and Magento 2</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px;">
<!-- Magento 1 Tree -->
<div>
<h3 style="margin-bottom: 15px; color: #667eea;">Magento 1 Categories</h3>
<div class="tree-container" id="m1-tree-container">
<div class="tree-loading">Loading categories...</div>
</div>
</div>
<!-- Magento 2 Tree -->
<div>
<h3 style="margin-bottom: 15px; color: #764ba2;">Magento 2 Categories</h3>
<div class="tree-container" id="m2-tree-container">
<div class="tree-loading">Loading categories...</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
<script> <script>
@ -474,6 +657,158 @@ function addLogEntry(message, type = '') {
logContent.appendChild(entry); logContent.appendChild(entry);
logContent.scrollTop = logContent.scrollHeight; logContent.scrollTop = logContent.scrollHeight;
} }
// Tab switching
function switchTab(tabName, tabElement) {
// Hide all tabs
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
// Show selected tab
document.getElementById('tab-' + tabName).classList.add('active');
tabElement.classList.add('active');
// Load category trees if switching to categories tab
if (tabName === 'categories') {
loadCategoryTrees();
}
}
// Load category trees
function loadCategoryTrees() {
loadM1Tree();
loadM2Tree();
}
// Load Magento 1 tree
function loadM1Tree() {
const container = document.getElementById('m1-tree-container');
container.innerHTML = '<div class="tree-loading">Loading categories...</div>';
fetch('{{ route("migration.magento1-category-tree") }}')
.then(response => response.json())
.then(data => {
if (data.success) {
container.innerHTML = '';
if (data.tree && data.tree.length > 0) {
renderTree(container, data.tree);
} else {
container.innerHTML = '<div class="tree-loading">No categories found</div>';
}
} else {
container.innerHTML = `<div class="tree-error">Error: ${data.message || 'Failed to load categories'}</div>`;
}
})
.catch(error => {
container.innerHTML = `<div class="tree-error">Error: ${error.message}</div>`;
});
}
// Load Magento 2 tree
function loadM2Tree() {
const container = document.getElementById('m2-tree-container');
container.innerHTML = '<div class="tree-loading">Loading categories...</div>';
fetch('{{ route("migration.magento2-category-tree") }}')
.then(response => response.json())
.then(data => {
if (data.success) {
container.innerHTML = '';
if (data.tree && data.tree.length > 0) {
renderTree(container, data.tree);
} else {
container.innerHTML = '<div class="tree-loading">No categories found</div>';
}
} else {
container.innerHTML = `<div class="tree-error">Error: ${data.message || 'Failed to load categories'}</div>`;
}
})
.catch(error => {
container.innerHTML = `<div class="tree-error">Error: ${error.message}</div>`;
});
}
// Render tree structure
function renderTree(container, nodes) {
nodes.forEach(node => {
const nodeElement = createTreeNode(node);
container.appendChild(nodeElement);
});
}
// Create a tree node element
function createTreeNode(node) {
const nodeDiv = document.createElement('div');
nodeDiv.className = 'tree-node';
const hasChildren = node.children && node.children.length > 0;
const itemDiv = document.createElement('div');
itemDiv.className = 'tree-node-item';
const toggle = document.createElement('span');
toggle.className = hasChildren ? 'tree-toggle collapsed' : 'tree-toggle leaf';
if (hasChildren) {
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.name || 'Unnamed Category';
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);
if (hasChildren) {
const childrenDiv = document.createElement('div');
childrenDiv.className = 'tree-children';
node.children.forEach(child => {
childrenDiv.appendChild(createTreeNode(child));
});
nodeDiv.appendChild(childrenDiv);
}
return nodeDiv;
}
// Toggle node expansion
function toggleNode(toggleElement) {
const nodeItem = toggleElement.parentElement;
const nodeDiv = nodeItem.parentElement;
const childrenDiv = nodeDiv.querySelector('.tree-children');
if (childrenDiv) {
const isExpanded = childrenDiv.classList.contains('expanded');
if (isExpanded) {
childrenDiv.classList.remove('expanded');
toggleElement.classList.remove('expanded');
toggleElement.classList.add('collapsed');
} else {
childrenDiv.classList.add('expanded');
toggleElement.classList.remove('collapsed');
toggleElement.classList.add('expanded');
}
}
}
</script> </script>
</body> </body>
</html> </html>

View File

@ -12,4 +12,6 @@
Route::post('/migrate', [MagentoMigrationController::class, 'migrate'])->name('migration.migrate'); Route::post('/migrate', [MagentoMigrationController::class, 'migrate'])->name('migration.migrate');
Route::get('/test-connections', [MagentoMigrationController::class, 'testConnections'])->name('migration.test-connections'); Route::get('/test-connections', [MagentoMigrationController::class, 'testConnections'])->name('migration.test-connections');
Route::get('/magento1-categories', [MagentoMigrationController::class, 'getMagento1Categories'])->name('migration.magento1-categories'); Route::get('/magento1-categories', [MagentoMigrationController::class, 'getMagento1Categories'])->name('migration.magento1-categories');
Route::get('/magento1-category-tree', [MagentoMigrationController::class, 'getMagento1CategoryTree'])->name('migration.magento1-category-tree');
Route::get('/magento2-category-tree', [MagentoMigrationController::class, 'getMagento2CategoryTree'])->name('migration.magento2-category-tree');
}); });