feat: Implement core admin panel functionalities including appointment, contact, and pricing management with associated models, controllers, views, and routes.

This commit is contained in:
LNHA
2026-02-03 14:58:00 +07:00
parent d1b931d547
commit df8e1f9665
25 changed files with 4574 additions and 659 deletions

View File

@@ -1,5 +1,6 @@
const { addBaseUrlToImages } = require("../utils/imageHelper");
const Contact = require("../models/contact");
const ContactSubmission = require("../models/contactSubmission");
// Get contact data from MongoDB
const getContactData = async () => {
@@ -60,6 +61,7 @@ exports.index = async (req, res) => {
zoom: 15,
location: "",
markerTitle: "",
embedUrl: "",
tileLayer: {
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
attribution: "",
@@ -70,16 +72,38 @@ exports.index = async (req, res) => {
form: {
sectionLabel: "",
heading: "",
description: "",
fields: [],
submitButton: { text: "Send Message" },
submitButton: { text: "Send Message", icon: "fa-solid fa-arrow-right", buttonClass: "theme-btn style-2" },
},
};
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
const { startDate, endDate } = req.query;
const query = {};
if (startDate || endDate) {
query.createdAt = {};
if (startDate) {
query.createdAt.$gte = new Date(startDate);
}
if (endDate) {
// Set end date to end of day
const end = new Date(endDate);
end.setHours(23, 59, 59, 999);
query.createdAt.$lte = end;
}
}
const submissions = await ContactSubmission.find(query).sort({ createdAt: -1 }).limit(50);
const frontendUrl = process.env.FRONTEND_URL;
res.render("admin/contact/index", {
title: "Contact Management",
layout: "layouts/main",
data,
submissions,
startDate,
endDate,
frontendUrl,
currentPath: req.path,
user: req.session.user,
@@ -140,6 +164,7 @@ exports.update = async (req, res) => {
zoom: 15,
location: "",
markerTitle: "",
embedUrl: "",
tileLayer: {
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
attribution: "",
@@ -150,8 +175,9 @@ exports.update = async (req, res) => {
form: formData || {
sectionLabel: "",
heading: "",
description: "",
fields: [],
submitButton: { text: "Send Message" },
submitButton: { text: "Send Message", icon: "fa-solid fa-arrow-right", buttonClass: "theme-btn style-2" },
},
});
} else {
@@ -179,3 +205,141 @@ exports.update = async (req, res) => {
res.redirect("/admin/contact");
}
};
// API để submit contact form (từ frontend)
exports.submitForm = async (req, res) => {
try {
const { name, email, phone, address, date, message } = req.body;
// Validation
if (!name || !email) {
return res.status(400).json({
success: false,
error: "Name and email are required",
});
}
// Create new submission
const submission = new ContactSubmission({
name: name.trim(),
email: email.trim().toLowerCase(),
phone: phone?.trim() || "",
address: address?.trim() || "",
date: date?.trim() || "",
message: message?.trim() || "",
ipAddress: req.ip || req.connection?.remoteAddress || "",
userAgent: req.get("User-Agent") || "",
});
await submission.save();
res.status(201).json({
success: true,
message: "Thank you for contacting us! We will get back to you soon.",
data: {
id: submission._id,
name: submission.name,
email: submission.email,
},
});
} catch (err) {
console.error("Error submitting contact form:", err);
// Handle validation errors
if (err.name === "ValidationError") {
const errors = Object.values(err.errors).map((e) => e.message);
return res.status(400).json({
success: false,
error: errors.join(", "),
});
}
res.status(500).json({
success: false,
error: "Error submitting form. Please try again later.",
});
}
};
// API để lấy danh sách submissions (cho admin)
exports.getSubmissions = async (req, res) => {
try {
const { status, page = 1, limit = 20 } = req.query;
const query = {};
if (status && ["pending", "read", "replied", "archived"].includes(status)) {
query.status = status;
}
const skip = (parseInt(page) - 1) * parseInt(limit);
const [submissions, total] = await Promise.all([
ContactSubmission.find(query)
.sort({ createdAt: -1 })
.skip(skip)
.limit(parseInt(limit)),
ContactSubmission.countDocuments(query),
]);
res.json({
success: true,
data: submissions,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
totalPages: Math.ceil(total / parseInt(limit)),
},
});
} catch (err) {
console.error("Error getting submissions:", err);
res.status(500).json({
success: false,
error: "Error loading submissions",
});
}
};
// API để cập nhật status của submission
exports.updateSubmissionStatus = async (req, res) => {
try {
const { id } = req.params;
const { status, notes } = req.body;
const validStatuses = ["pending", "read", "replied", "archived"];
if (!validStatuses.includes(status)) {
return res.status(400).json({
success: false,
error: "Invalid status",
});
}
const updateData = { status };
if (notes !== undefined) updateData.notes = notes;
if (status === "replied") updateData.repliedAt = new Date();
const submission = await ContactSubmission.findByIdAndUpdate(
id,
updateData,
{ new: true }
);
if (!submission) {
return res.status(404).json({
success: false,
error: "Submission not found",
});
}
res.json({
success: true,
data: submission,
});
} catch (err) {
console.error("Error updating submission:", err);
res.status(500).json({
success: false,
error: "Error updating submission",
});
}
};