added category listing
This commit is contained in:
parent
f8241e4305
commit
44c7c7354a
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -251,6 +251,154 @@
|
|||
color: #666;
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -261,6 +409,14 @@
|
|||
</div>
|
||||
|
||||
<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 -->
|
||||
<div class="section">
|
||||
<h2>📡 Database Connections</h2>
|
||||
|
|
@ -363,6 +519,33 @@
|
|||
</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>
|
||||
|
||||
<script>
|
||||
|
|
@ -474,6 +657,158 @@ function addLogEntry(message, type = '') {
|
|||
logContent.appendChild(entry);
|
||||
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>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -12,4 +12,6 @@
|
|||
Route::post('/migrate', [MagentoMigrationController::class, 'migrate'])->name('migration.migrate');
|
||||
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-category-tree', [MagentoMigrationController::class, 'getMagento1CategoryTree'])->name('migration.magento1-category-tree');
|
||||
Route::get('/magento2-category-tree', [MagentoMigrationController::class, 'getMagento2CategoryTree'])->name('migration.magento2-category-tree');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue