forked from UKSOURCE/cms.hailearning.edu.vn
refactor(header): improve code formatting and add JSON response support
This commit is contained in:
@@ -35,7 +35,28 @@
|
|||||||
font-weight: var(--font-weight-medium);
|
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-success {
|
||||||
.bg-soft-danger { background-color: var(--danger-soft); color: var(--danger-color); border: 1px solid rgba(220, 53, 69, 0.2); }
|
background-color: var(--success-soft);
|
||||||
.bg-soft-warning { background-color: var(--warning-soft); color: var(--warning-color); border: 1px solid rgba(255, 193, 7, 0.2); }
|
color: var(--success-color);
|
||||||
.bg-soft-info { background-color: var(--info-soft); color: var(--info-color); border: 1px solid rgba(23, 162, 184, 0.2); }
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ const slugify = require("slugify");
|
|||||||
*/
|
*/
|
||||||
const buildMenuTree = (items, parentId = null, isPublic = false) => {
|
const buildMenuTree = (items, parentId = null, isPublic = false) => {
|
||||||
const branch = [];
|
const branch = [];
|
||||||
const children = items.filter(item =>
|
const children = items.filter(
|
||||||
String(item.parentId) === String(parentId) || (item.parentId === null && parentId === null)
|
(item) => String(item.parentId) === String(parentId) || (item.parentId === null && parentId === null),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
@@ -20,7 +20,7 @@ const buildMenuTree = (items, parentId = null, isPublic = false) => {
|
|||||||
id: item._id,
|
id: item._id,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
url: item.url,
|
url: item.url,
|
||||||
type: item.type
|
type: item.type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,8 +57,8 @@ exports.index = async (req, res) => {
|
|||||||
// 2. Create Menu Item
|
// 2. Create Menu Item
|
||||||
exports.store = async (req, res) => {
|
exports.store = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
console.log('=== BACKEND: store hit ===');
|
console.log("=== BACKEND: store hit ===");
|
||||||
console.log('Body:', req.body);
|
console.log("Body:", req.body);
|
||||||
const { title, url, parentId, order, status, type } = req.body;
|
const { title, url, parentId, order, status, type } = req.body;
|
||||||
const slug = slugify(title, { lower: true, strict: true });
|
const slug = slugify(title, { lower: true, strict: true });
|
||||||
|
|
||||||
@@ -69,15 +69,27 @@ exports.store = async (req, res) => {
|
|||||||
parentId: parentId || null,
|
parentId: parentId || null,
|
||||||
order: order || 0,
|
order: order || 0,
|
||||||
status: status || "active",
|
status: status || "active",
|
||||||
type: type || "internal"
|
type: type || "internal",
|
||||||
});
|
});
|
||||||
|
|
||||||
const savedItem = await newItem.save();
|
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");
|
req.flash("success_msg", "Menu item created successfully");
|
||||||
res.redirect("/admin/header?tab=menu");
|
res.redirect("/admin/header?tab=menu");
|
||||||
} catch (error) {
|
} 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);
|
req.flash("error_msg", "Failed to create menu item: " + error.message);
|
||||||
res.redirect("/admin/header?tab=menu");
|
res.redirect("/admin/header?tab=menu");
|
||||||
}
|
}
|
||||||
@@ -87,8 +99,8 @@ exports.store = async (req, res) => {
|
|||||||
exports.update = async (req, res) => {
|
exports.update = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
console.log('=== BACKEND: update hit ===', { id });
|
console.log("=== BACKEND: update hit ===", { id });
|
||||||
console.log('Body:', req.body);
|
console.log("Body:", req.body);
|
||||||
const { title, url, parentId, order, status, type } = req.body;
|
const { title, url, parentId, order, status, type } = req.body;
|
||||||
|
|
||||||
const updateData = {
|
const updateData = {
|
||||||
@@ -96,7 +108,7 @@ exports.update = async (req, res) => {
|
|||||||
parentId: parentId || null,
|
parentId: parentId || null,
|
||||||
order,
|
order,
|
||||||
status,
|
status,
|
||||||
type
|
type,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (title) {
|
if (title) {
|
||||||
@@ -107,15 +119,33 @@ exports.update = async (req, res) => {
|
|||||||
const updated = await HeaderMenu.findByIdAndUpdate(id, updateData, { new: true });
|
const updated = await HeaderMenu.findByIdAndUpdate(id, updateData, { new: true });
|
||||||
|
|
||||||
if (!updated) {
|
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");
|
req.flash("error_msg", "Menu item not found");
|
||||||
} else {
|
} 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");
|
req.flash("success_msg", "Menu item updated successfully");
|
||||||
}
|
}
|
||||||
res.redirect("/admin/header?tab=menu");
|
res.redirect("/admin/header?tab=menu");
|
||||||
} catch (error) {
|
} 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);
|
req.flash("error_msg", "Update failed: " + error.message);
|
||||||
res.redirect("/admin/header?tab=menu");
|
res.redirect("/admin/header?tab=menu");
|
||||||
}
|
}
|
||||||
@@ -126,16 +156,16 @@ exports.destroy = async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { id } = req.body;
|
const { id } = req.body;
|
||||||
const menuId = id || req.params.id;
|
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 deleteRecursive(menuId);
|
||||||
await HeaderMenu.findByIdAndDelete(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");
|
req.flash("success_msg", "Menu item and its sub-menu deleted successfully");
|
||||||
res.redirect("/admin/header?tab=menu");
|
res.redirect("/admin/header?tab=menu");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('=== DELETE MENU ERROR ===', error);
|
console.error("=== DELETE MENU ERROR ===", error);
|
||||||
req.flash("error_msg", "Delete failed: " + error.message);
|
req.flash("error_msg", "Delete failed: " + error.message);
|
||||||
res.redirect("/admin/header?tab=menu");
|
res.redirect("/admin/header?tab=menu");
|
||||||
}
|
}
|
||||||
@@ -147,11 +177,11 @@ exports.reorder = async (req, res) => {
|
|||||||
const { items } = req.body; // Array of { id, order, parentId }
|
const { items } = req.body; // Array of { id, order, parentId }
|
||||||
|
|
||||||
if (items && Array.isArray(items)) {
|
if (items && Array.isArray(items)) {
|
||||||
const bulkOps = items.map(item => ({
|
const bulkOps = items.map((item) => ({
|
||||||
updateOne: {
|
updateOne: {
|
||||||
filter: { _id: item.id },
|
filter: { _id: item.id },
|
||||||
update: { order: item.order, parentId: item.parentId || null }
|
update: { order: item.order, parentId: item.parentId || null },
|
||||||
}
|
},
|
||||||
}));
|
}));
|
||||||
await HeaderMenu.bulkWrite(bulkOps);
|
await HeaderMenu.bulkWrite(bulkOps);
|
||||||
return res.json({ success: true, message: "Reordered successfully" });
|
return res.json({ success: true, message: "Reordered successfully" });
|
||||||
|
|||||||
@@ -269,6 +269,11 @@
|
|||||||
const targetId = event.target.getAttribute('href').substring(1);
|
const targetId = event.target.getAttribute('href').substring(1);
|
||||||
document.getElementById('activeTabInput').value = targetId;
|
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
|
// Only load Menu Tree if clicking on the menu tab
|
||||||
if (targetId === 'menu') {
|
if (targetId === 'menu') {
|
||||||
loadMenuTree();
|
loadMenuTree();
|
||||||
@@ -365,6 +370,12 @@
|
|||||||
showNotification('All changes saved successfully', 'success');
|
showNotification('All changes saved successfully', 'success');
|
||||||
submitBtn.classList.remove('btn-primary');
|
submitBtn.classList.remove('btn-primary');
|
||||||
submitBtn.classList.add('btn-outline-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 {
|
} else {
|
||||||
const errorMsg = (!headerResult.success ? headerResult.message : '') || (!menuResult.success ? menuResult.message : '') || 'Unable to save some changes';
|
const errorMsg = (!headerResult.success ? headerResult.message : '') || (!menuResult.success ? menuResult.message : '') || 'Unable to save some changes';
|
||||||
showNotification('Error: ' + errorMsg, 'error');
|
showNotification('Error: ' + errorMsg, 'error');
|
||||||
@@ -1113,19 +1124,29 @@
|
|||||||
console.log('Response:', response.data);
|
console.log('Response:', response.data);
|
||||||
|
|
||||||
if (response.data.success || response.status === 200) {
|
if (response.data.success || response.status === 200) {
|
||||||
showToast('Success', 'Menu information has been updated', 'success');
|
showNotification('Menu item saved successfully', 'success');
|
||||||
|
|
||||||
// Hide modal
|
// Hide modal
|
||||||
const modalElement = document.getElementById('modalAddMenu');
|
const modalElement = document.getElementById('modalAddMenu');
|
||||||
const modal = bootstrap.Modal.getOrCreateInstance(modalElement);
|
const modal = bootstrap.Modal.getOrCreateInstance(modalElement);
|
||||||
modal.hide();
|
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 {
|
} else {
|
||||||
showToast('Error', response.data.message || 'Unable to save menu', 'error');
|
showNotification(response.data.message || 'Unable to save menu', 'error');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('AJAX Error:', 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 {
|
} finally {
|
||||||
if (submitBtn) {
|
if (submitBtn) {
|
||||||
submitBtn.disabled = false;
|
submitBtn.disabled = false;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
<span class="badge bg-light text-dark border ms-2" style="font-size: 0.7rem;">External</span>
|
<span class="badge bg-light text-dark border ms-2" style="font-size: 0.7rem;">External</span>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if (item.status === 'inactive') { %>
|
<% if (item.status === 'inactive') { %>
|
||||||
<span class="badge bg-soft-secondary ms-2">Inactive</span>
|
<span class="badge ms-2 bg-soft-danger text-danger">Inactive</span>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<span class="badge bg-soft-success ms-2">Active</span>
|
<span class="badge bg-soft-success ms-2">Active</span>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|||||||
Reference in New Issue
Block a user