fea/nhat-dat-11042026-merge #1

Closed
minhnhat wants to merge 27 commits from UKSOURCE/cms.hailearning.edu.vn:fea/nhat-dat-11042026-merge into fea/nhat-13042028-merge-kiet-thien
4 changed files with 106 additions and 15 deletions
Showing only changes of commit 0109b08b58 - Show all commits

View File

@@ -1,5 +1,6 @@
const { getServiceData } = require("../services/service.service");
const Service = require("../models/service");
const syncServiceMenu = require("../services/syncServiceMenu");
const { addBaseUrlToImages, getFullImageUrl } = require("../utils/imageHelper");
const writeAuditLog = require("../audit/writeAuditLog");
const diffObject = require("../audit/diffObject");
@@ -98,6 +99,8 @@ exports.updateService = async (req, res) => {
changes,
req,
});
// Sync header menu children to reflect updated service name/slug
await syncServiceMenu(updatedData.services?.items || []);
req.flash("success_msg", "Service updated successfully");
res.redirect("/admin/service");
} catch (err) {
@@ -168,6 +171,9 @@ exports.update = async (req, res) => {
await Service.create(updatedData);
}
// Sync header menu children to reflect current service list
await syncServiceMenu(updatedData.services?.items || []);
req.flash("success_msg", "Service updated successfully");
res.redirect("/admin/service");
} catch (err) {

View File

@@ -0,0 +1,34 @@
/**
* One-time script: sync service menu items from DB into HeaderMenu.
* Run: node scripts/sync-service-menu-now.js
*/
const mongoose = require("mongoose");
const dotenv = require("dotenv");
dotenv.config();
const Service = require("../models/service");
const syncServiceMenu = require("../services/syncServiceMenu");
const MONGODB_URI = process.env.MONGODB_URI || "mongodb://localhost:27017/hailearning";
async function run() {
await mongoose.connect(MONGODB_URI);
console.log("✅ Connected to MongoDB");
const serviceDoc = await Service.findOne().lean();
if (!serviceDoc?.services?.items?.length) {
console.log("⚠️ No services found in DB.");
process.exit(0);
}
console.log(`Found ${serviceDoc.services.items.length} services. Syncing menu...`);
await syncServiceMenu(serviceDoc.services.items);
console.log("✅ Done.");
process.exit(0);
}
run().catch((err) => {
console.error("❌ Error:", err);
process.exit(1);
});

View File

@@ -0,0 +1,57 @@
/**
* Sync HeaderMenu children of the "Services" menu item
* to match the current list of services in the database.
*
* Strategy:
* - Find the HeaderMenu item whose url === '/services'
* - Delete all its direct children
* - Re-create one child per service item (url = /services/<slug>)
*/
const HeaderMenu = require("../models/headerMenu");
const slugify = require("slugify");
/**
* @param {Array} serviceItems - array of service objects { slug, name }
*/
const syncServiceMenu = async (serviceItems = []) => {
try {
// 1. Find the "Services" parent menu item
const servicesParent = await HeaderMenu.findOne({ url: "/services" });
if (!servicesParent) {
console.warn("[syncServiceMenu] No HeaderMenu item with url=/services found. Skipping sync.");
return;
}
const parentId = servicesParent._id;
// 2. Remove all existing children of that parent
await HeaderMenu.deleteMany({ parentId });
// 3. Re-create one child per service
const ops = serviceItems
.filter((s) => s && s.slug && s.name)
.map((s, index) => ({
title: s.name,
slug: slugify(s.name, { lower: true, strict: true }),
url: `/services/${s.slug}`,
parentId,
order: index + 1,
status: "active",
type: "internal",
is_maintainance: false,
}));
if (ops.length > 0) {
await HeaderMenu.insertMany(ops);
}
console.log(`[syncServiceMenu] Synced ${ops.length} service menu items under parentId=${parentId}`);
} catch (err) {
// Non-fatal log but don't crash the main request
console.error("[syncServiceMenu] Error syncing service menu:", err.message);
}
};
module.exports = syncServiceMenu;

View File

@@ -2,6 +2,14 @@
<div class="tab-pane fade" id="floatingcontact" role="tabpanel">
<div class="row g-4">
<div class="col-md-12">
<div class="card border shadow-sm mb-1">
<div class="card-header bg-white d-flex justify-content-center align-items-center">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="floatingContactEnabled"
<%=(data.floatingContact?.enabled !== false ) ? 'checked' : '' %>>
</div>
</div>
</div>
<div class="card border shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h6 class="mb-0">
@@ -11,21 +19,7 @@
</div>
<div class="card-body">
<div class="row g-4 align-items-start">
<div class="col-12">
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3 p-3 border rounded-3 bg-light-subtle">
<div>
<div class="fw-semibold">Widget visibility</div>
<small class="text-muted">Enable or disable the floating contact widget on the homepage.</small>
</div>
<div class="form-check form-switch m-0">
<input class="form-check-input" type="checkbox" id="floatingContactEnabled"
<%= data.floatingContact?.enabled !== false ? 'checked' : '' %> />
<label class="form-check-label fw-medium" for="floatingContactEnabled">
Enable floating widget
</label>
</div>
</div>
</div>
<div class="col-lg-8">
<label class="form-label fw-medium">Panel Title</label>
<input type="text" class="form-control" id="floatingContactPanelTitle"