forked from UKSOURCE/cms.hailearning.edu.vn
206 lines
6.7 KiB
JavaScript
206 lines
6.7 KiB
JavaScript
const HeaderMenu = require("../models/HeaderMenu");
|
|
const slugify = require("slugify");
|
|
|
|
/**
|
|
* Helper: Build tree structure from flat array
|
|
*/
|
|
const buildMenuTree = (items, parentId = null, isPublic = false) => {
|
|
const branch = [];
|
|
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) {
|
|
cleanItem = {
|
|
id: item._id,
|
|
title: item.title,
|
|
url: item.url,
|
|
type: item.type,
|
|
};
|
|
}
|
|
|
|
const subChildren = buildMenuTree(items, item._id, isPublic);
|
|
cleanItem.children = subChildren.length > 0 ? subChildren : [];
|
|
branch.push(cleanItem);
|
|
}
|
|
return branch.sort((a, b) => a.order - b.order);
|
|
};
|
|
|
|
/**
|
|
* Helper: Recursive delete children
|
|
*/
|
|
const deleteRecursive = async (parentId) => {
|
|
const children = await HeaderMenu.find({ parentId });
|
|
for (const child of children) {
|
|
await deleteRecursive(child._id);
|
|
await HeaderMenu.findByIdAndDelete(child._id);
|
|
}
|
|
};
|
|
|
|
// 1. Render Menu Tab logic
|
|
exports.index = async (req, res) => {
|
|
try {
|
|
const items = await HeaderMenu.find().sort({ order: 1 });
|
|
const menuTree = buildMenuTree(items);
|
|
return { menuTree, flatItems: items };
|
|
} catch (error) {
|
|
console.error("Error fetching menu items:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// 2. Create Menu Item
|
|
exports.store = async (req, res) => {
|
|
try {
|
|
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 });
|
|
|
|
const newItem = new HeaderMenu({
|
|
title,
|
|
slug,
|
|
url,
|
|
parentId: parentId || null,
|
|
order: order || 0,
|
|
status: status || "active",
|
|
type: type || "internal",
|
|
});
|
|
|
|
const savedItem = await newItem.save();
|
|
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);
|
|
|
|
// 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");
|
|
}
|
|
};
|
|
|
|
// 3. Update Menu Item
|
|
exports.update = async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
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,
|
|
};
|
|
|
|
if (title) {
|
|
updateData.title = title;
|
|
updateData.slug = slugify(title, { lower: true, strict: true });
|
|
}
|
|
|
|
const updated = await HeaderMenu.findByIdAndUpdate(id, updateData, { new: true });
|
|
|
|
if (!updated) {
|
|
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);
|
|
|
|
// 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);
|
|
|
|
// 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");
|
|
}
|
|
};
|
|
|
|
// 4. Delete Menu Item (Cascade delete children)
|
|
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 });
|
|
|
|
await deleteRecursive(menuId);
|
|
await HeaderMenu.findByIdAndDelete(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);
|
|
req.flash("error_msg", "Delete failed: " + error.message);
|
|
res.redirect("/admin/header?tab=menu");
|
|
}
|
|
};
|
|
|
|
// 5. Reorder Menu
|
|
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) => ({
|
|
updateOne: {
|
|
filter: { _id: item.id },
|
|
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 });
|
|
}
|
|
};
|
|
|
|
// Public API: Get active menu as clean tree
|
|
exports.api = async (req, res) => {
|
|
try {
|
|
const items = await HeaderMenu.find({ status: "active" }).sort({ order: 1 });
|
|
const tree = buildMenuTree(items, null, true);
|
|
res.json({ success: true, data: tree });
|
|
} catch (error) {
|
|
res.status(500).json({ success: false, message: error.message });
|
|
}
|
|
};
|