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
<% } %>