diff --git a/assets/css/components/table.css b/assets/css/components/table.css index f77f7a5..4aede1d 100644 --- a/assets/css/components/table.css +++ b/assets/css/components/table.css @@ -35,7 +35,28 @@ font-weight: var(--font-weight-medium); } -.bg-soft-success { background-color: var(--success-soft); color: var(--success-color); border: 1px solid rgba(40, 167, 69, 0.2); } -.bg-soft-danger { background-color: var(--danger-soft); color: var(--danger-color); border: 1px solid rgba(220, 53, 69, 0.2); } -.bg-soft-warning { background-color: var(--warning-soft); color: var(--warning-color); border: 1px solid rgba(255, 193, 7, 0.2); } -.bg-soft-info { background-color: var(--info-soft); color: var(--info-color); border: 1px solid rgba(23, 162, 184, 0.2); } +.bg-soft-success { + background-color: var(--success-soft); + color: var(--success-color); + border: 1px solid rgba(40, 167, 69, 0.2); +} +.bg-soft-danger { + background-color: var(--danger-soft); + color: var(--danger-color); + border: 1px solid rgba(220, 53, 69, 0.2); +} +.bg-soft-warning { + background-color: var(--warning-soft); + color: var(--warning-color); + border: 1px solid rgba(255, 193, 7, 0.2); +} +.bg-soft-info { + background-color: var(--info-soft); + color: var(--info-color); + border: 1px solid rgba(23, 162, 184, 0.2); +} +.bg-soft-secondary { + background-color: #f8f9fa; + color: #6c757d; + border: 1px solid rgba(108, 117, 125, 0.2); +} diff --git a/controllers/headerMenuController.js b/controllers/headerMenuController.js index 43c246a..5423ed4 100644 --- a/controllers/headerMenuController.js +++ b/controllers/headerMenuController.js @@ -6,13 +6,13 @@ const slugify = require("slugify"); */ const buildMenuTree = (items, parentId = null, isPublic = false) => { const branch = []; - const children = items.filter(item => - String(item.parentId) === String(parentId) || (item.parentId === null && parentId === null) + const children = items.filter( + (item) => String(item.parentId) === String(parentId) || (item.parentId === null && parentId === null), ); for (const child of children) { const item = child.toObject ? child.toObject() : { ...child }; - + // Clean data for public API if requested let cleanItem = item; if (isPublic) { @@ -20,7 +20,7 @@ const buildMenuTree = (items, parentId = null, isPublic = false) => { id: item._id, title: item.title, url: item.url, - type: item.type + type: item.type, }; } @@ -57,8 +57,8 @@ exports.index = async (req, res) => { // 2. Create Menu Item exports.store = async (req, res) => { try { - console.log('=== BACKEND: store hit ==='); - console.log('Body:', req.body); + console.log("=== BACKEND: store hit ==="); + console.log("Body:", req.body); const { title, url, parentId, order, status, type } = req.body; const slug = slugify(title, { lower: true, strict: true }); @@ -69,15 +69,27 @@ exports.store = async (req, res) => { parentId: parentId || null, order: order || 0, status: status || "active", - type: type || "internal" + type: type || "internal", }); const savedItem = await newItem.save(); - console.log('=== MENU CREATED ===', savedItem); + console.log("=== MENU CREATED ===", savedItem); + + // Return JSON for AJAX requests + if (req.xhr || req.headers.accept?.indexOf("json") > -1) { + return res.json({ success: true, message: "Menu item created successfully", data: savedItem }); + } + req.flash("success_msg", "Menu item created successfully"); res.redirect("/admin/header?tab=menu"); } catch (error) { - console.error('=== CREATE MENU ERROR ===', error); + console.error("=== CREATE MENU ERROR ===", error); + + // Return JSON for AJAX requests + if (req.xhr || req.headers.accept?.indexOf("json") > -1) { + return res.status(400).json({ success: false, message: error.message }); + } + req.flash("error_msg", "Failed to create menu item: " + error.message); res.redirect("/admin/header?tab=menu"); } @@ -87,16 +99,16 @@ exports.store = async (req, res) => { exports.update = async (req, res) => { try { const { id } = req.params; - console.log('=== BACKEND: update hit ===', { id }); - console.log('Body:', req.body); + console.log("=== BACKEND: update hit ===", { id }); + console.log("Body:", req.body); const { title, url, parentId, order, status, type } = req.body; - - const updateData = { - url, - parentId: parentId || null, - order, - status, - type + + const updateData = { + url, + parentId: parentId || null, + order, + status, + type, }; if (title) { @@ -105,17 +117,35 @@ exports.update = async (req, res) => { } const updated = await HeaderMenu.findByIdAndUpdate(id, updateData, { new: true }); - + if (!updated) { - console.log('=== UPDATE MENU NOT FOUND ===', id); + console.log("=== UPDATE MENU NOT FOUND ===", id); + + // Return JSON for AJAX requests + if (req.xhr || req.headers.accept?.indexOf("json") > -1) { + return res.status(404).json({ success: false, message: "Menu item not found" }); + } + req.flash("error_msg", "Menu item not found"); } else { - console.log('=== MENU UPDATED ===', updated); + console.log("=== MENU UPDATED ===", updated); + + // Return JSON for AJAX requests + if (req.xhr || req.headers.accept?.indexOf("json") > -1) { + return res.json({ success: true, message: "Menu item updated successfully", data: updated }); + } + req.flash("success_msg", "Menu item updated successfully"); } res.redirect("/admin/header?tab=menu"); } catch (error) { - console.error('=== UPDATE MENU ERROR ===', error); + console.error("=== UPDATE MENU ERROR ===", error); + + // Return JSON for AJAX requests + if (req.xhr || req.headers.accept?.indexOf("json") > -1) { + return res.status(400).json({ success: false, message: error.message }); + } + req.flash("error_msg", "Update failed: " + error.message); res.redirect("/admin/header?tab=menu"); } @@ -126,16 +156,16 @@ exports.destroy = async (req, res) => { try { const { id } = req.body; const menuId = id || req.params.id; - console.log('=== BACKEND: destroy hit ===', { menuId, body: req.body }); + console.log("=== BACKEND: destroy hit ===", { menuId, body: req.body }); await deleteRecursive(menuId); await HeaderMenu.findByIdAndDelete(menuId); - console.log('=== MENU DELETED ===', menuId); + console.log("=== MENU DELETED ===", menuId); req.flash("success_msg", "Menu item and its sub-menu deleted successfully"); res.redirect("/admin/header?tab=menu"); } catch (error) { - console.error('=== DELETE MENU ERROR ===', error); + console.error("=== DELETE MENU ERROR ===", error); req.flash("error_msg", "Delete failed: " + error.message); res.redirect("/admin/header?tab=menu"); } @@ -145,18 +175,18 @@ exports.destroy = async (req, res) => { exports.reorder = async (req, res) => { try { const { items } = req.body; // Array of { id, order, parentId } - + if (items && Array.isArray(items)) { - const bulkOps = items.map(item => ({ + const bulkOps = items.map((item) => ({ updateOne: { filter: { _id: item.id }, - update: { order: item.order, parentId: item.parentId || null } - } + update: { order: item.order, parentId: item.parentId || null }, + }, })); await HeaderMenu.bulkWrite(bulkOps); return res.json({ success: true, message: "Reordered successfully" }); } - + res.status(400).json({ success: false, message: "Invalid data" }); } catch (error) { res.status(500).json({ success: false, message: error.message }); diff --git a/views/admin/header/index.ejs b/views/admin/header/index.ejs index 8fe3439..03cdb93 100644 --- a/views/admin/header/index.ejs +++ b/views/admin/header/index.ejs @@ -268,6 +268,11 @@ tab.addEventListener('shown.bs.tab', function (event) { const targetId = event.target.getAttribute('href').substring(1); document.getElementById('activeTabInput').value = targetId; + + // Update URL without reload to preserve tab state + const url = new URL(window.location); + url.searchParams.set('tab', targetId); + window.history.replaceState({}, '', url); // Only load Menu Tree if clicking on the menu tab if (targetId === 'menu') { @@ -365,6 +370,12 @@ showNotification('All changes saved successfully', 'success'); submitBtn.classList.remove('btn-primary'); submitBtn.classList.add('btn-outline-primary'); + + // Reload to refresh data, preserve current tab + const currentTab = document.getElementById('activeTabInput').value; + setTimeout(() => { + window.location.href = window.location.pathname + '?tab=' + currentTab; + }, 1000); } else { const errorMsg = (!headerResult.success ? headerResult.message : '') || (!menuResult.success ? menuResult.message : '') || 'Unable to save some changes'; showNotification('Error: ' + errorMsg, 'error'); @@ -1113,19 +1124,29 @@ console.log('Response:', response.data); if (response.data.success || response.status === 200) { - showToast('Success', 'Menu information has been updated', 'success'); + showNotification('Menu item saved successfully', 'success'); + // Hide modal const modalElement = document.getElementById('modalAddMenu'); const modal = bootstrap.Modal.getOrCreateInstance(modalElement); modal.hide(); - // Refresh data or reload tab - setTimeout(() => window.location.reload(), 1000); + + // Mark as changed so user needs to click Save Changes + if (typeof window.markHeaderChanged === 'function') { + window.markHeaderChanged(); + } + + // Reload page to show updated menu structure, preserve current tab + const currentTab = document.getElementById('activeTabInput').value; + setTimeout(() => { + window.location.href = window.location.pathname + '?tab=' + currentTab; + }, 1000); } else { - showToast('Error', response.data.message || 'Unable to save menu', 'error'); + showNotification(response.data.message || 'Unable to save menu', 'error'); } } catch (error) { console.error('AJAX Error:', error); - showToast('Error', 'Server connection error: ' + (error.response?.data?.message || error.message), 'error'); + showNotification('Server connection error: ' + (error.response?.data?.message || error.message), 'error'); } finally { if (submitBtn) { submitBtn.disabled = false; diff --git a/views/admin/header/menu.ejs b/views/admin/header/menu.ejs index 0c4104e..1aa6c0b 100644 --- a/views/admin/header/menu.ejs +++ b/views/admin/header/menu.ejs @@ -38,7 +38,7 @@ External <% } %> <% if (item.status === 'inactive') { %> - Inactive + Inactive <% } else { %> Active <% } %>