forked from CSI-KJSCE/BOS-React-
final changes
This commit is contained in:
16
backend/config/db.js
Normal file
16
backend/config/db.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const connectDB = async () => {
|
||||
try {
|
||||
await mongoose.connect(process.env.MONGODB_URI, {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
});
|
||||
console.log("MongoDB connected");
|
||||
} catch (err) {
|
||||
console.error("MongoDB connection error:", err);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = connectDB;
|
||||
78
backend/controllers/departmentController.js
Normal file
78
backend/controllers/departmentController.js
Normal file
@@ -0,0 +1,78 @@
|
||||
const DepartmentEmail = require("../models/DepartmentEmail");
|
||||
const Program = require("../models/Program");
|
||||
|
||||
exports.getAllDepartmentEmails = async (req, res) => {
|
||||
try {
|
||||
const departmentEmails = await DepartmentEmail.find();
|
||||
res.json(departmentEmails);
|
||||
} catch (error) {
|
||||
console.error("Error fetching department emails:", error);
|
||||
res.status(500).json({ error: "Error fetching department emails" });
|
||||
}
|
||||
};
|
||||
|
||||
exports.getDepartmentsByProgram = async (req, res) => {
|
||||
const program = await Program.findOne({ name: req.params.program });
|
||||
res.json(program ? program.departments : []);
|
||||
};
|
||||
|
||||
exports.getEmailsByDepartment = async (req, res) => {
|
||||
const departmentEmails = await DepartmentEmail.findOne({ department: req.params.department });
|
||||
res.json(departmentEmails ? { emails: departmentEmails.emails } : { emails: [] });
|
||||
};
|
||||
|
||||
exports.updateDepartmentEmails = async (req, res) => {
|
||||
const { department } = req.params;
|
||||
const { emails } = req.body;
|
||||
|
||||
if (!emails || !Array.isArray(emails)) {
|
||||
return res.status(400).json({ error: "Emails array is required" });
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedDepartment = await DepartmentEmail.findOneAndUpdate(
|
||||
{ department },
|
||||
{ emails },
|
||||
{ new: true, upsert: true }
|
||||
);
|
||||
res.json(updatedDepartment);
|
||||
} catch (error) {
|
||||
console.error("Error updating department emails:", error);
|
||||
res.status(500).json({ error: "Error updating department emails" });
|
||||
}
|
||||
};
|
||||
|
||||
exports.createDepartment = async (req, res) => {
|
||||
const { department, emails, program } = req.body;
|
||||
|
||||
if (!department || !emails || !Array.isArray(emails) || !program) {
|
||||
return res.status(400).json({ error: "Department, program, and emails array are required" });
|
||||
}
|
||||
|
||||
try {
|
||||
const newDepartment = new DepartmentEmail({ department, emails });
|
||||
await newDepartment.save();
|
||||
|
||||
await Program.findOneAndUpdate(
|
||||
{ name: program },
|
||||
{ $addToSet: { departments: department } },
|
||||
{ upsert: true }
|
||||
);
|
||||
|
||||
res.status(201).json(newDepartment);
|
||||
} catch (error) {
|
||||
console.error("Error creating department:", error);
|
||||
res.status(500).json({ error: "Error creating department" });
|
||||
}
|
||||
};
|
||||
|
||||
exports.deleteDepartment = async (req, res) => {
|
||||
try {
|
||||
const { department } = req.params;
|
||||
await DepartmentEmail.findOneAndDelete({ department });
|
||||
res.json({ message: "Department deleted successfully" });
|
||||
} catch (error) {
|
||||
console.error("Error deleting department:", error);
|
||||
res.status(500).json({ error: "Error deleting department" });
|
||||
}
|
||||
};
|
||||
136
backend/controllers/emailController.js
Normal file
136
backend/controllers/emailController.js
Normal file
@@ -0,0 +1,136 @@
|
||||
const nodemailer = require("nodemailer");
|
||||
const moment = require("moment");
|
||||
const Meeting = require("../models/Meeting");
|
||||
const DepartmentEmail = require("../models/DepartmentEmail");
|
||||
|
||||
const sendEmail = async (req, res) => {
|
||||
try {
|
||||
const { program, department, subject, body, agenda, date, startTime, endTime, recipients } = req.body;
|
||||
const files = req.files;
|
||||
|
||||
console.log({ program, department, subject, body, agenda, date, startTime, endTime, recipients, files });
|
||||
|
||||
if (!program || !department || !subject || !body || !date || !startTime || !endTime || !recipients) {
|
||||
return res.status(400).json({ error: "All fields are required!" });
|
||||
}
|
||||
|
||||
const departmentEmails = await DepartmentEmail.findOne({ department });
|
||||
if (!departmentEmails) {
|
||||
return res.status(404).json({ error: "Department emails not found" });
|
||||
}
|
||||
|
||||
const allRecipients = [...new Set([...departmentEmails.emails, ...recipients])];
|
||||
|
||||
const conflict = await Meeting.findOne({
|
||||
date,
|
||||
startTime: { $lt: endTime },
|
||||
endTime: { $gt: startTime },
|
||||
});
|
||||
|
||||
const formattedDate = moment(date, "YYYY-MM-DD").format("dddd, MMMM Do YYYY");
|
||||
const formattedStartTime = moment(startTime, "HH:mm").format("hh:mm A");
|
||||
const formattedEndTime = moment(endTime, "HH:mm").format("hh:mm A");
|
||||
|
||||
const formattedStart = moment(`${date} ${startTime}`, "YYYY-MM-DD HH:mm").utc().format("YYYYMMDDTHHmmss[Z]");
|
||||
const formattedEnd = moment(`${date} ${endTime}`, "YYYY-MM-DD HH:mm").utc().format("YYYYMMDDTHHmmss[Z]");
|
||||
|
||||
const zoomLink = "https://us05web.zoom.us/j/2655616202?pwd=S3d6RjVPNnhmQ3AzTlZsRC9GYmNHQT09";
|
||||
const calendarLink = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(
|
||||
subject
|
||||
)}&details=${encodeURIComponent(`${body}\nJoin Zoom Meeting: ${zoomLink}`)}&dates=${formattedStart}/${formattedEnd}`;
|
||||
|
||||
const formattedAgenda = Array.isArray(agenda) && agenda.length > 0
|
||||
? agenda.map((item, index) => `<li>${index + 1}. ${item}</li>`).join("")
|
||||
: "<li>No agenda provided</li>";
|
||||
|
||||
const newMeeting = new Meeting({
|
||||
program,
|
||||
department,
|
||||
subject,
|
||||
body,
|
||||
agenda,
|
||||
date,
|
||||
startTime,
|
||||
endTime,
|
||||
recipients,
|
||||
attachments: files.map((file) => ({ filename: file.filename, path: file.path })),
|
||||
});
|
||||
await newMeeting.save();
|
||||
|
||||
let conflictDetails = null;
|
||||
|
||||
if (conflict) {
|
||||
conflictDetails = {
|
||||
department: conflict.department,
|
||||
date: conflict.date,
|
||||
startTime: conflict.startTime,
|
||||
endTime: conflict.endTime,
|
||||
recipients: conflict.recipients,
|
||||
subject: conflict.subject,
|
||||
agenda: conflict.agenda || "No agenda provided",
|
||||
};
|
||||
console.log("Conflict detected! Details:", conflictDetails);
|
||||
}
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
service: "gmail",
|
||||
auth: {
|
||||
user: process.env.EMAIL,
|
||||
pass: process.env.EMAIL_PASSWORD,
|
||||
},
|
||||
});
|
||||
|
||||
const mailOptions = {
|
||||
from: process.env.EMAIL,
|
||||
to: recipients,
|
||||
subject,
|
||||
html: `
|
||||
<p><strong>Respected Board Members,</strong></p>
|
||||
<p><strong>Greetings from Somaiya Vidyavihar University !!</strong></p>
|
||||
<p><strong>We are glad to invite you to the Board of Studies meeting: </strong></p>
|
||||
<p>${body}</p>
|
||||
<p><strong>Program:</strong> ${program}</p>
|
||||
<p><strong>Department:</strong> ${department}</p>
|
||||
<p><strong>Date:</strong> ${formattedDate}</p>
|
||||
<p><strong>Time:</strong> ${formattedStartTime} - ${formattedEndTime}</p>
|
||||
<p><strong>Zoom Meeting Details:</strong></p>
|
||||
<p><strong>Meeting ID: </strong>265 561 6202</p>
|
||||
<p><strong>Meeting Password: </strong>123456</p>
|
||||
<p><strong>Zoom Meeting Link: </strong><a href="${zoomLink}" target="_blank">Join the Meeting</a></p>
|
||||
<p><strong>Agenda:</strong></p>
|
||||
<ul>${formattedAgenda}</ul>
|
||||
<p><a href="${calendarLink}">📅 Click here to add this event to Google Calendar</a></p>
|
||||
${
|
||||
conflictDetails
|
||||
? `<p><strong>Conflict detected!</strong></p>
|
||||
<p><strong>Conflicting Meeting Details:</strong></p>
|
||||
<ul>
|
||||
<li><strong>Department:</strong> ${conflictDetails.department}</li>
|
||||
<li><strong>Date:</strong> ${conflictDetails.date}</li>
|
||||
<li><strong>Time:</strong> ${conflictDetails.startTime} - ${conflictDetails.endTime}</li>
|
||||
<li><strong>Subject:</strong> ${conflictDetails.subject}</li>
|
||||
<li><strong>Agenda:</strong> ${conflictDetails.agenda}</li>
|
||||
</ul>`
|
||||
: ""
|
||||
}
|
||||
`,
|
||||
attachments: files.map((file) => ({
|
||||
filename: file.filename,
|
||||
path: file.path,
|
||||
})),
|
||||
};
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
|
||||
res.status(200).json({
|
||||
message: conflictDetails ? "Conflict detected! Email sent successfully." : "Email sent successfully!",
|
||||
conflictDetails,
|
||||
calendarLink,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error sending email:", error);
|
||||
res.status(500).json({ error: "Error sending email" });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { sendEmail };
|
||||
18
backend/controllers/meetingController.js
Normal file
18
backend/controllers/meetingController.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const Meeting = require("../models/Meeting");
|
||||
|
||||
exports.getMeetings = async (req, res) => {
|
||||
try {
|
||||
const meetings = await Meeting.find();
|
||||
const formattedMeetings = meetings.map((meeting) => {
|
||||
const formattedAgenda = meeting.agenda
|
||||
? meeting.agenda.map((item, index) => `<li>${index + 1}. ${item}</li>`).join("")
|
||||
: "<li>No agenda provided</li>";
|
||||
return { ...meeting.toObject(), formattedAgenda };
|
||||
});
|
||||
|
||||
res.json(formattedMeetings);
|
||||
} catch (error) {
|
||||
console.error("Error fetching meetings:", error);
|
||||
res.status(500).json({ error: "Error fetching meetings" });
|
||||
}
|
||||
};
|
||||
11
backend/middleware/upload.js
Normal file
11
backend/middleware/upload.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const multer = require("multer");
|
||||
const path = require("path");
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: "uploads/",
|
||||
filename: (req, file, cb) => {
|
||||
cb(null, Date.now() + path.extname(file.originalname));
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = multer({ storage });
|
||||
8
backend/models/DepartmentEmail.js
Normal file
8
backend/models/DepartmentEmail.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const DepartmentEmailSchema = new mongoose.Schema({
|
||||
department: String,
|
||||
emails: [String]
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("DepartmentEmail", DepartmentEmailSchema);
|
||||
16
backend/models/Meeting.js
Normal file
16
backend/models/Meeting.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const meetingSchema = new mongoose.Schema({
|
||||
program: String,
|
||||
department: String,
|
||||
subject: String,
|
||||
body: String,
|
||||
agenda: [String],
|
||||
date: String,
|
||||
startTime: String,
|
||||
endTime: String,
|
||||
recipients: [String],
|
||||
attachments: [{ filename: String, path: String }],
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("Meeting", meetingSchema);
|
||||
26
backend/models/Program.js
Normal file
26
backend/models/Program.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const express = require("express");
|
||||
const {
|
||||
getAllDepartmentEmails,
|
||||
getDepartmentsByProgram,
|
||||
getEmailsByDepartment,
|
||||
updateDepartmentEmails,
|
||||
createDepartment,
|
||||
deleteDepartment,
|
||||
} = require("../controllers/departmentController");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/programs", async (req, res) => {
|
||||
const Program = require("../models/Program");
|
||||
const programs = await Program.find();
|
||||
res.json(programs);
|
||||
});
|
||||
|
||||
router.get("/departments/:program", getDepartmentsByProgram);
|
||||
router.get("/department-emails/:department", getEmailsByDepartment);
|
||||
router.get("/department-emails", getAllDepartmentEmails);
|
||||
router.put("/department-emails/:department", updateDepartmentEmails);
|
||||
router.post("/department-emails", createDepartment);
|
||||
router.delete("/department-emails/:department", deleteDepartment);
|
||||
|
||||
module.exports = router;
|
||||
26
backend/routes/departmentRoutes.js
Normal file
26
backend/routes/departmentRoutes.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const express = require("express");
|
||||
const {
|
||||
getAllDepartmentEmails,
|
||||
getDepartmentsByProgram,
|
||||
getEmailsByDepartment,
|
||||
updateDepartmentEmails,
|
||||
createDepartment,
|
||||
deleteDepartment,
|
||||
} = require("../controllers/departmentController");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/programs", async (req, res) => {
|
||||
const Program = require("../models/Program");
|
||||
const programs = await Program.find();
|
||||
res.json(programs);
|
||||
});
|
||||
|
||||
router.get("/departments/:program", getDepartmentsByProgram);
|
||||
router.get("/department-emails/:department", getEmailsByDepartment);
|
||||
router.get("/department-emails", getAllDepartmentEmails);
|
||||
router.put("/department-emails/:department", updateDepartmentEmails);
|
||||
router.post("/department-emails", createDepartment);
|
||||
router.delete("/department-emails/:department", deleteDepartment);
|
||||
|
||||
module.exports = router;
|
||||
9
backend/routes/emailRoutes.js
Normal file
9
backend/routes/emailRoutes.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const express = require("express");
|
||||
const upload = require("../middleware/upload");
|
||||
const { sendEmail } = require("../controllers/emailController");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post("/send-email", upload.array("attachments"), sendEmail);
|
||||
|
||||
module.exports = router;
|
||||
8
backend/routes/meetingRoutes.js
Normal file
8
backend/routes/meetingRoutes.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const express = require("express");
|
||||
const { getMeetings } = require("../controllers/meetingController");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/meetings", getMeetings);
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,185 +1,26 @@
|
||||
//server.js
|
||||
|
||||
require("dotenv").config();
|
||||
const express = require("express");
|
||||
const mongoose = require("mongoose");
|
||||
const bodyParser = require("body-parser");
|
||||
const cors = require("cors");
|
||||
const nodemailer = require("nodemailer");
|
||||
const multer = require("multer");
|
||||
const bodyParser = require("body-parser");
|
||||
const path = require("path");
|
||||
const moment = require("moment");
|
||||
|
||||
const connectDB = require("./config/db");
|
||||
const emailRoutes = require("./routes/emailRoutes");
|
||||
const meetingRoutes = require("./routes/meetingRoutes");
|
||||
const departmentRoutes = require("./routes/departmentRoutes");
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use("/uploads", express.static("uploads"));
|
||||
app.use("/uploads", express.static(path.join(__dirname, "uploads")));
|
||||
|
||||
// Multer setup for file uploads
|
||||
const storage = multer.diskStorage({
|
||||
destination: "uploads/",
|
||||
filename: (req, file, cb) => {
|
||||
cb(null, Date.now() + path.extname(file.originalname));
|
||||
},
|
||||
});
|
||||
const upload = multer({ storage });
|
||||
connectDB();
|
||||
|
||||
// MongoDB connection
|
||||
mongoose
|
||||
.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true })
|
||||
.then(() => console.log("Connected to MongoDB"))
|
||||
.catch((err) => console.error("MongoDB Connection Error:", err));
|
||||
app.use("/api", emailRoutes);
|
||||
app.use("/api", meetingRoutes);
|
||||
app.use("/api", departmentRoutes);
|
||||
|
||||
// Meeting Schema
|
||||
const meetingSchema = new mongoose.Schema({
|
||||
program: String,
|
||||
department: String,
|
||||
subject: String,
|
||||
body: String,
|
||||
agenda: [String],
|
||||
date: String,
|
||||
startTime: String,
|
||||
endTime: String,
|
||||
recipients: [String],
|
||||
attachments: [{ filename: String, path: String }],
|
||||
});
|
||||
const Meeting = mongoose.model("Meeting", meetingSchema);
|
||||
|
||||
app.get("/api/meetings", async (req, res) => {
|
||||
try {
|
||||
const meetings = await Meeting.find(); // Fetching all meetings
|
||||
|
||||
// Format agenda in HTML for each meeting
|
||||
const formattedMeetings = meetings.map((meeting) => {
|
||||
const formattedAgenda = meeting.agenda
|
||||
? meeting.agenda.map((item, index) => `<li>${index + 1}. ${item}</li>`).join("")
|
||||
: "<li>No agenda provided</li>";
|
||||
return { ...meeting.toObject(), formattedAgenda }; // Add formatted agenda to each meeting
|
||||
});
|
||||
|
||||
res.json(formattedMeetings); // Send formatted meetings
|
||||
} catch (error) {
|
||||
console.error("Error fetching meetings:", error);
|
||||
res.status(500).json({ error: "Error fetching meetings" });
|
||||
}
|
||||
});
|
||||
|
||||
// API to send email
|
||||
app.post("/api/send-email", upload.array("attachments"), async (req, res) => {
|
||||
try {
|
||||
const { program, department, subject, body, agenda, date, startTime, endTime, recipients } = req.body;
|
||||
const files = req.files;
|
||||
|
||||
console.log({ program, department, subject, body, agenda, date, startTime, endTime, recipients, files });
|
||||
|
||||
if (!program || !department || !subject || !body || !date || !startTime || !endTime || !recipients) {
|
||||
return res.status(400).json({ error: "All fields are required!" });
|
||||
}
|
||||
|
||||
// Check for meeting conflicts
|
||||
const conflict = await Meeting.findOne({
|
||||
date,
|
||||
startTime: { $lt: endTime },
|
||||
endTime: { $gt: startTime },
|
||||
});
|
||||
|
||||
if (conflict) {
|
||||
// Return the details of the conflicting meeting
|
||||
const conflictDetails = {
|
||||
department: conflict.department,
|
||||
date: conflict.date,
|
||||
startTime: conflict.startTime,
|
||||
endTime: conflict.endTime,
|
||||
recipients: conflict.recipients,
|
||||
subject: conflict.subject,
|
||||
};
|
||||
|
||||
return res.status(409).json({
|
||||
error: "Meeting conflict detected! Please choose a different time.",
|
||||
conflictDetails
|
||||
});
|
||||
}
|
||||
|
||||
// Format date and time properly
|
||||
const formattedDate = moment(date, "YYYY-MM-DD").format("dddd, MMMM Do YYYY");
|
||||
const formattedStartTime = moment(startTime, "HH:mm").format("hh:mm A");
|
||||
const formattedEndTime = moment(endTime, "HH:mm").format("hh:mm A");
|
||||
|
||||
// Format Google Calendar event link
|
||||
const formattedStart = moment(`${date} ${startTime}`, "YYYY-MM-DD HH:mm").utc().format("YYYYMMDDTHHmmss[Z]");
|
||||
const formattedEnd = moment(`${date} ${endTime}`, "YYYY-MM-DD HH:mm").utc().format("YYYYMMDDTHHmmss[Z]");
|
||||
const zoomLink = "https://us05web.zoom.us/j/2655616202?pwd=S3d6RjVPNnhmQ3AzTlZsRC9GYmNHQT09";
|
||||
const calendarLink = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(
|
||||
subject
|
||||
)}&details=${encodeURIComponent(`${body}\nJoin Zoom Meeting: ${zoomLink}`)}&dates=${formattedStart}/${formattedEnd}`;
|
||||
|
||||
// Format agenda into pointwise list
|
||||
const formattedAgenda = agenda
|
||||
? agenda.map((item, index) => `<li>${index + 1}. ${item}</li>`).join("")
|
||||
: "<li>No agenda provided</li>";
|
||||
|
||||
// Store meeting in DB
|
||||
const newMeeting = new Meeting({
|
||||
program,
|
||||
department,
|
||||
subject,
|
||||
body,
|
||||
agenda,
|
||||
date,
|
||||
startTime,
|
||||
endTime,
|
||||
recipients,
|
||||
attachments: files.map((file) => ({ filename: file.filename, path: file.path })),
|
||||
});
|
||||
await newMeeting.save();
|
||||
|
||||
// Email transporter
|
||||
const transporter = nodemailer.createTransport({
|
||||
service: "gmail",
|
||||
auth: {
|
||||
user: process.env.EMAIL,
|
||||
pass: process.env.EMAIL_PASSWORD,
|
||||
},
|
||||
});
|
||||
|
||||
// Email options
|
||||
const mailOptions = {
|
||||
from: process.env.EMAIL,
|
||||
to: recipients,
|
||||
subject,
|
||||
html: `
|
||||
<p><strong>Respected Board Members,</strong></p>
|
||||
<p><strong>Greetings from Somaiya Vidyavihar University !!</strong></p>
|
||||
<p><strong>We are glad to invite you to the Board of Studies meeting: </strong></p>
|
||||
<p>${body}</p>
|
||||
<p><strong>Program:</strong> ${program}</p>
|
||||
<p><strong>Department:</strong> ${department}</p>
|
||||
<p><strong>Date:</strong> ${formattedDate}</p>
|
||||
<p><strong>Time:</strong> ${formattedStartTime} - ${formattedEndTime}</p>
|
||||
<p><strong>Zoom Meeting Details:</strong></p>
|
||||
<p><strong>Meeting ID: </strong>265 561 6202</p>
|
||||
<p><strong>Meeting Password: </strong>123456</p>
|
||||
<p><strong>Zoom Meeting Link: </strong><a href="https://us05web.zoom.us/j/2655616202?pwd=S3d6RjVPNnhmQ3AzTlZsRC9GYmNHQT09" target="_blank">Join the Meeting</a></p>
|
||||
<p><strong>Agenda:</strong></p>
|
||||
<ul>${formattedAgenda}</ul>
|
||||
<p><a href="${calendarLink}">📅 Click here to add this event to Google Calendar</a></p>
|
||||
`,
|
||||
attachments: files.map((file) => ({
|
||||
filename: file.filename,
|
||||
path: file.path,
|
||||
})),
|
||||
};
|
||||
|
||||
// Send email
|
||||
await transporter.sendMail(mailOptions);
|
||||
res.status(200).json({ message: "Email sent successfully!", calendarLink });
|
||||
} catch (error) {
|
||||
console.error("Error sending email:", error);
|
||||
res.status(500).json({ error: "Error sending email" });
|
||||
}
|
||||
});
|
||||
|
||||
// Start server
|
||||
const PORT = process.env.PORT || 5000;
|
||||
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
|
||||
|
||||
BIN
backend/uploads/1743699686010.docx
Normal file
BIN
backend/uploads/1743699686010.docx
Normal file
Binary file not shown.
BIN
backend/uploads/1743699709610.docx
Normal file
BIN
backend/uploads/1743699709610.docx
Normal file
Binary file not shown.
BIN
backend/uploads/1743699844366.docx
Normal file
BIN
backend/uploads/1743699844366.docx
Normal file
Binary file not shown.
BIN
backend/uploads/1748321326875.pdf
Normal file
BIN
backend/uploads/1748321326875.pdf
Normal file
Binary file not shown.
BIN
backend/uploads/1748321328791.pdf
Normal file
BIN
backend/uploads/1748321328791.pdf
Normal file
Binary file not shown.
@@ -1,22 +1,25 @@
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
|
||||
import Page1 from './Page1';
|
||||
import Page1 from './Page1';
|
||||
import Page2 from './Page2';
|
||||
import Page3 from './Page3';
|
||||
import Page4 from './Page4';
|
||||
import Page5 from './Page5';
|
||||
import { GoogleOAuthProvider } from '@react-oauth/google';
|
||||
import Page3 from './Page3';
|
||||
import Page4 from './Page4';
|
||||
import Page5 from './Page5';
|
||||
import Page6 from './Page6';
|
||||
import { GoogleOAuthProvider } from '@react-oauth/google';
|
||||
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<GoogleOAuthProvider clientId="YOUR_GOOGLE_OAUTH_CLIENT_ID">
|
||||
<GoogleOAuthProvider clientId="YOUR_GOOGLE_OAUTH_CLIENT_ID">
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={<Page1 />} />
|
||||
<Route path="/page2" element={<Page2 />} />
|
||||
<Route path="/page2" element={<Page2 />} />
|
||||
<Route path="/page3" element={<Page3 />} />
|
||||
<Route path="/page4" element={<Page4 />} />
|
||||
<Route path="/page5" element={<Page5 />} />
|
||||
<Route path="/page6" element={<Page6 />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</GoogleOAuthProvider>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/*Page1.css*/
|
||||
|
||||
/* General Styles */
|
||||
* {
|
||||
margin: 0;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//Page1.js
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { GoogleLogin } from '@react-oauth/google'; // Import GoogleLogin component
|
||||
@@ -31,7 +33,7 @@ const Page1 = () => {
|
||||
|
||||
// Allowed credentials
|
||||
const allowedCredentials = [
|
||||
{ email: "hodcomp@somaiya.edu", password: "hodcomp123!" },
|
||||
{ email: "hodcomp@somaiya.edu", password: "hodcomp123" },
|
||||
{ email: "director.kjsse@somaiya.edu", password: "directorkjsse2024" },
|
||||
{ email: "dean.engg@somaiya.edu", password: "deanengg2024" },
|
||||
{ email: "hodextc@somaiya.edu", password: "hodextc2024" },
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/*Page2.css*/
|
||||
|
||||
/* General Styles */
|
||||
body {
|
||||
margin: 0;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//Page2.js
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import './Page2.css'; // CSS File import
|
||||
@@ -30,7 +32,7 @@ const Page2 = () => {
|
||||
<li><Link to="/page2">Home</Link></li>
|
||||
<li><a href="#">Schedule</a></li>
|
||||
<li><Link to="/page5">Meeting DataBase</Link></li>
|
||||
<li><a href="#">Contact</a></li>
|
||||
<li><Link to="/page6">Edit DataBase</Link></li>
|
||||
<li><Link to="/">Log Out</Link></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* Page3.css */
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//Page3.js
|
||||
|
||||
import React, { useState } from "react";
|
||||
import './Page3.css'
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/*Page4.css*/
|
||||
|
||||
/* Container for the content area */
|
||||
.content-area {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
//Page4.js
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import axios from "axios";
|
||||
import './Page2.css';
|
||||
import './Page4.css'; // Import the same CSS file for consistent styling
|
||||
import { FaTimes } from "react-icons/fa"; // Red cross icon for agenda/removal
|
||||
import './Page4.css';
|
||||
import { FaTimes } from "react-icons/fa";
|
||||
import image from './components/image.png';
|
||||
import somaiyatrust from './components/somaiyatrust.png';
|
||||
import redlines from './components/redlines.png';
|
||||
|
||||
const programs = ["BTech", "MTech", "PhD"];
|
||||
const departments = [
|
||||
"Artificial Intelligence & Data Science",
|
||||
"Computer Engineering",
|
||||
"Computer & Communication Engineering",
|
||||
"Computer Science & Business Systems",
|
||||
"Electronics Engineering (VLSI Design & Technology)",
|
||||
"Electronics & Computer Engineering",
|
||||
"Electronics & Telecommunication Engineering",
|
||||
"Information Technology",
|
||||
"Mechanical Engineering",
|
||||
"Robotics & Artificial Intelligence",
|
||||
];
|
||||
import { useEffect } from "react";
|
||||
|
||||
const Page4 = () => {
|
||||
const [programs, setPrograms] = useState([]);
|
||||
const [departments, setDepartments] = useState([]);
|
||||
const [departmentEmails, setDepartmentEmails] = useState([]);
|
||||
const [program, setProgram] = useState("");
|
||||
const [department, setDepartment] = useState("");
|
||||
const [subject, setSubject] = useState("");
|
||||
@@ -34,6 +26,33 @@ const Page4 = () => {
|
||||
const [recipients, setRecipients] = useState([""]);
|
||||
const [attachments, setAttachments] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
axios.get("http://localhost:5000/api/programs")
|
||||
.then(response => setPrograms(response.data))
|
||||
.catch(error => console.error("Error fetching programs:", error));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (program) {
|
||||
axios.get(`http://localhost:5000/api/departments/${program}`)
|
||||
.then(response => setDepartments(response.data))
|
||||
.catch(error => console.error("Error fetching departments:", error));
|
||||
} else {
|
||||
setDepartments([]);
|
||||
}
|
||||
}, [program]);
|
||||
|
||||
useEffect(() => {
|
||||
if (department) {
|
||||
axios.get(`http://localhost:5000/api/department-emails/${department}`)
|
||||
.then(response => {
|
||||
setDepartmentEmails(response.data.emails);
|
||||
setRecipients([...response.data.emails]);
|
||||
})
|
||||
.catch(error => console.error("Error fetching department emails:", error));
|
||||
}
|
||||
}, [department]);
|
||||
|
||||
const addAgenda = () => setAgenda([...agenda, ""]);
|
||||
const handleAgendaChange = (index, value) => {
|
||||
const updatedAgenda = [...agenda];
|
||||
@@ -86,7 +105,7 @@ const Page4 = () => {
|
||||
} catch (err) {
|
||||
if (err.response?.status === 409) {
|
||||
const conflictDetails = err.response.data.conflictDetails;
|
||||
alert(`Conflict Detected:
|
||||
alert(`Conflict Detected :
|
||||
Department: ${conflictDetails.department}
|
||||
Date: ${conflictDetails.date}
|
||||
Time: ${conflictDetails.startTime} - ${conflictDetails.endTime}
|
||||
@@ -114,7 +133,7 @@ const Page4 = () => {
|
||||
<li><Link to="/page2">Home</Link></li>
|
||||
<li><Link to="/page3">Schedule</Link></li>
|
||||
<li><Link to="/page5">Meeting DataBase</Link></li>
|
||||
<li><a href="#">Contact</a></li>
|
||||
<li><Link to="/page6">Edit DataBase</Link></li>
|
||||
<li><Link to="/">Log Out</Link></li>
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -134,13 +153,11 @@ const Page4 = () => {
|
||||
{/* Content Area (Right Side) */}
|
||||
<div className="content-area">
|
||||
<h1>BOS Email Management System</h1>
|
||||
<label>Program:</label>
|
||||
<label>Programme:</label>
|
||||
<select onChange={(e) => setProgram(e.target.value)} value={program}>
|
||||
<option value="">Choose a Program</option>
|
||||
<option value="">Choose a Programme</option>
|
||||
{programs.map((prog, idx) => (
|
||||
<option key={idx} value={prog}>
|
||||
{prog}
|
||||
</option>
|
||||
<option key={idx} value={prog.name}>{prog.name}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
|
||||
@@ -1,141 +1,309 @@
|
||||
/* Ensure body has margin 0 and full height for scrolling */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
min-height: 100vh;
|
||||
overflow-y: auto; /* Ensure scrolling */
|
||||
/*Page5.css*/
|
||||
|
||||
/* Base Styles */
|
||||
:root {
|
||||
--primary-red: #B7202E;
|
||||
--dark-red: #8a0000;
|
||||
--light-red: #d32f2f;
|
||||
--white: #ffffff;
|
||||
--light-gray: #f5f5f5;
|
||||
--medium-gray: #e0e0e0;
|
||||
--dark-gray: #757575;
|
||||
--black: #212121;
|
||||
--shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
--transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* Page Container */
|
||||
.page-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: auto; /* Let height grow naturally */
|
||||
overflow-x: hidden; /* Hide only horizontal scroll */
|
||||
overflow-y: auto; /* Allow vertical scroll */
|
||||
}
|
||||
|
||||
/* Header Bar */
|
||||
.header-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 20px;
|
||||
background-color: var(--white);
|
||||
box-shadow: var(--shadow);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.leftlogo, .rightlogo {
|
||||
height: 50px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.college-name {
|
||||
color: var(--primary-red);
|
||||
font-size: 1.5rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Navigation Bar */
|
||||
.navbar {
|
||||
background-color: var(--primary-red);
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.navbar ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.navbar li {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.navbar a {
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.navbar a:hover, .navbar a.active {
|
||||
color: var(--white);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 5px;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.meetings-page-container {
|
||||
flex: 1;
|
||||
padding: 30px;
|
||||
max-width: 1400px;
|
||||
margin: 40px auto 0 auto; /* 40px top margin to separate from navbar */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
text-align: center;
|
||||
color: var(--primary-red);
|
||||
margin-bottom: 30px;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.no-meetings {
|
||||
text-align: center;
|
||||
color: var(--dark-gray);
|
||||
font-size: 1.2rem;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
/* Meetings Grid Container */
|
||||
.meetings-grid-container {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.meetings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 25px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Meeting Cards */
|
||||
.meeting-card {
|
||||
background-color: var(--white);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
border-left: 4px solid var(--primary-red);
|
||||
overflow: hidden;
|
||||
transition: var(--transition);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.meeting-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.card-summary {
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.meeting-title {
|
||||
color: var(--black);
|
||||
font-size: 1.2rem;
|
||||
margin: 0 0 10px 0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.meeting-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: var(--dark-gray);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.meeting-department {
|
||||
color: var(--primary-red);
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.card-details {
|
||||
padding: 20px;
|
||||
background-color: var(--light-gray);
|
||||
border-top: 1px solid var(--medium-gray);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-weight: 600;
|
||||
color: var(--primary-red);
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.detail-section h3 {
|
||||
color: var(--primary-red);
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid var(--medium-gray);
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.agenda-list, .attachments-list {
|
||||
padding-left: 20px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.agenda-list li, .attachments-list li {
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.attachments-list a {
|
||||
color: var(--primary-red);
|
||||
text-decoration: none;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.attachments-list a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.recipients-list {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* Calendar Button */
|
||||
.calendar-button {
|
||||
display: block;
|
||||
background-color: var(--primary-red);
|
||||
color: var(--white) !important;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin-top: 20px;
|
||||
text-decoration: none;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.calendar-button:hover {
|
||||
background-color: var(--dark-red);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
background-color: var(--primary-red);
|
||||
color: var(--white);
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.terms {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.terms a {
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer-image {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 1024px) {
|
||||
.meetings-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
}
|
||||
|
||||
/* Main container for the meetings */
|
||||
.meetings-container {
|
||||
background-color: #ffffff;
|
||||
border-radius: 10px; /* Rounded corners */
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #e0e0e0; /* Light border for the box */
|
||||
box-sizing: border-box;
|
||||
max-width: 1200px;
|
||||
margin: 20px auto; /* Added margin to ensure some space from top */
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Heading for the page */
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 2rem;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* List of meetings */
|
||||
.meetings-list {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header-bar {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* Styling for each meeting box */
|
||||
.meeting-box {
|
||||
background-color: #ffffff;
|
||||
padding: 20px;
|
||||
border-radius: 10px; /* Rounded corners */
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
border-left: 5px solid #B7202E; /* Changed to the new color */
|
||||
overflow: hidden; /* Prevents overflow */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Styling for meeting titles */
|
||||
.meeting-box h2 {
|
||||
font-size: 1.5rem;
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Styling for meeting details like Program, Date, etc. */
|
||||
.meeting-box p {
|
||||
font-size: 1rem;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/* Styling for links like Google Calendar and attachments */
|
||||
.meeting-box a {
|
||||
text-decoration: none;
|
||||
color: #B7202E; /* Changed to the new color */
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.meeting-box a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Button for Google Calendar link */
|
||||
.google-calendar-link {
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: #B7202E; /* Changed to the new color */
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.google-calendar-link:hover {
|
||||
background-color: #9e1c25; /* Darkened shade of the same color */
|
||||
}
|
||||
|
||||
/* Ensure that meetings overflow properly on the page */
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.meeting-box ul {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.meeting-box ul li {
|
||||
font-size: 1rem;
|
||||
color: #444;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
/* Responsive Design for smaller screens */
|
||||
@media (max-width: 768px) {
|
||||
.meeting-box {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.meeting-box h2 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.meeting-box p,
|
||||
.meeting-box ul li {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.google-calendar-link {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Button for Google Calendar link */
|
||||
.google-calendar-link {
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: #B7202E; /* Red color */
|
||||
color: white !important; /* Text color set to white */
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
.college-name {
|
||||
margin: 10px 0;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.google-calendar-link:hover {
|
||||
background-color: #9e1c25; /* Darkened shade of red on hover */
|
||||
}
|
||||
|
||||
.navbar li {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.meetings-page-container {
|
||||
padding: 20px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.meetings-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.meeting-meta {
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
@@ -1,83 +1,164 @@
|
||||
//Page5.js
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import './Page5.css';
|
||||
import somaiyatrust from './components/somaiyatrust.png';
|
||||
import image from './components/image.png';
|
||||
import redlines from './components/redlines.png';
|
||||
|
||||
const Page5 = () => {
|
||||
const [meetings, setMeetings] = useState([]);
|
||||
const [expandedIndex, setExpandedIndex] = useState(null);
|
||||
|
||||
// Fetch meetings data from the server
|
||||
useEffect(() => {
|
||||
axios.get('http://localhost:5000/api/meetings') // Updated to backend URL
|
||||
axios.get('http://localhost:5000/api/meetings')
|
||||
.then((response) => {
|
||||
setMeetings(response.data);
|
||||
// Sort meetings by date (newest first) and time
|
||||
const sortedMeetings = response.data.sort((a, b) => {
|
||||
const dateA = new Date(`${a.date}T${a.startTime}`);
|
||||
const dateB = new Date(`${b.date}T${b.startTime}`);
|
||||
return dateB - dateA;
|
||||
});
|
||||
setMeetings(sortedMeetings);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching meetings:', error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const toggleExpand = (index) => {
|
||||
setExpandedIndex(prev => (prev === index ? null : index));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="meetings-container">
|
||||
<h1>Meeting Details</h1>
|
||||
<div className="meetings-list">
|
||||
<div className="page-container">
|
||||
{/* Header Bar */}
|
||||
<header className="header-bar">
|
||||
<img src={image} alt="Left Logo" className="leftlogo" />
|
||||
<h1 className="college-name">Somaiya Scheduler</h1>
|
||||
<img src={somaiyatrust} alt="Right Logo" className="rightlogo" />
|
||||
</header>
|
||||
|
||||
{/* Navigation Bar */}
|
||||
<nav className="navbar">
|
||||
<ul>
|
||||
<li><Link to="/page2">Home</Link></li>
|
||||
<li><Link to="/page3">Schedule</Link></li>
|
||||
<li><Link to="/page5" className="active">Meeting DataBase</Link></li>
|
||||
<li><Link to="/page6">Manage Emails</Link></li>
|
||||
<li><a href="#">Contact</a></li>
|
||||
<li><Link to="/">Log Out</Link></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="meetings-page-container">
|
||||
<h1 className="page-title">Meeting Details</h1>
|
||||
|
||||
{meetings.length === 0 ? (
|
||||
<p>No meetings available</p>
|
||||
<p className="no-meetings">No meetings available</p>
|
||||
) : (
|
||||
meetings.map((meeting, index) => (
|
||||
<div key={index} className="meeting-box">
|
||||
<h2>{meeting.subject}</h2>
|
||||
<p><strong>Program:</strong> {meeting.program}</p>
|
||||
<p><strong>Department:</strong> {meeting.department}</p>
|
||||
<p><strong>Date:</strong> {meeting.date}</p>
|
||||
<p><strong>Time:</strong> {meeting.startTime} - {meeting.endTime}</p>
|
||||
<div className="meetings-grid-container">
|
||||
<div className="meetings-grid">
|
||||
{meetings.map((meeting, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`meeting-card ${expandedIndex === index ? 'expanded' : ''}`}
|
||||
>
|
||||
<div className="card-summary" onClick={() => toggleExpand(index)}>
|
||||
<h2 className="meeting-title">{meeting.subject}</h2>
|
||||
<div className="meeting-meta">
|
||||
<span className="meeting-date">{meeting.date}</span>
|
||||
<span className="meeting-time">{meeting.startTime} - {meeting.endTime}</span>
|
||||
</div>
|
||||
<div className="meeting-department">{meeting.department}</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Agenda:</strong></p>
|
||||
{meeting.agenda && meeting.agenda.length > 0 ? (
|
||||
<ul>
|
||||
{meeting.agenda.map((item, idx) => (
|
||||
<li key={idx}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p>No agenda provided</p>
|
||||
)}
|
||||
{expandedIndex === index && (
|
||||
<div className="card-details">
|
||||
<div className="detail-row">
|
||||
<span className="detail-label">Program:</span>
|
||||
<span>{meeting.program}</span>
|
||||
</div>
|
||||
|
||||
<p><strong>Body:</strong> {meeting.body}</p>
|
||||
<div className="detail-section">
|
||||
<h3>Agenda</h3>
|
||||
{meeting.agenda && meeting.agenda.length > 0 ? (
|
||||
<ul className="agenda-list">
|
||||
{meeting.agenda.map((item, idx) => (
|
||||
<li key={idx}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p>No agenda provided</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p><strong>Recipients:</strong> {meeting.recipients}</p>
|
||||
<div className="detail-section">
|
||||
<h3>Details</h3>
|
||||
<p>{meeting.body}</p>
|
||||
</div>
|
||||
|
||||
{/* Display Attachments if available */}
|
||||
<p><strong>Attachments:</strong></p>
|
||||
{meeting.attachments && meeting.attachments.length > 0 ? (
|
||||
<ul>
|
||||
{meeting.attachments.map((file, idx) => (
|
||||
<li key={idx}>
|
||||
<a href={`/uploads/${file.filename}`} target="_blank" rel="noopener noreferrer">
|
||||
{file.filename}
|
||||
<div className="detail-section">
|
||||
<h3>Recipients</h3>
|
||||
<div className="recipients-list">
|
||||
{meeting.recipients.join(', ')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="detail-section">
|
||||
<h3>Attachments</h3>
|
||||
{meeting.attachments && meeting.attachments.length > 0 ? (
|
||||
<ul className="attachments-list">
|
||||
{meeting.attachments.map((file, idx) => (
|
||||
<li key={idx}>
|
||||
<a
|
||||
href={`/uploads/${file.filename}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{file.filename}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p>No attachments available</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={`https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(
|
||||
meeting.subject
|
||||
)}&details=${encodeURIComponent(meeting.body)}&dates=${meeting.date}T${meeting.startTime}/${meeting.date}T${meeting.endTime}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="calendar-button"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
Add to Google Calendar
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p>No attachments available</p>
|
||||
)}
|
||||
|
||||
<a
|
||||
href={`https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(
|
||||
meeting.subject
|
||||
)}&details=${encodeURIComponent(meeting.body)}&dates=${meeting.date}T${meeting.startTime}/${meeting.date}T${meeting.endTime}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="google-calendar-link"
|
||||
>
|
||||
Add to Google Calendar
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<footer>
|
||||
<div className="terms">
|
||||
<a href="#">Terms and Policies</a>
|
||||
<img src={redlines} alt="Footer Logo" className="footer-image" />
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page5;
|
||||
export default Page5;
|
||||
307
frontend/src/Page6.css
Normal file
307
frontend/src/Page6.css
Normal file
@@ -0,0 +1,307 @@
|
||||
/*Page6.css*/
|
||||
|
||||
/* Root Variables (optional, for consistency) */
|
||||
:root {
|
||||
--primary-red: #B7202E;
|
||||
--dark-red: #8a0000;
|
||||
--white: #ffffff;
|
||||
--light-gray: #f5f5f5;
|
||||
--medium-gray: #e0e0e0;
|
||||
--dark-gray: #757575;
|
||||
--shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
--transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* Page Container */
|
||||
.page-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
/* Header Bar */
|
||||
.header-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 20px;
|
||||
background-color: var(--white);
|
||||
box-shadow: var(--shadow);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.leftlogo, .rightlogo {
|
||||
height: 50px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.college-name {
|
||||
color: var(--primary-red);
|
||||
font-size: 1.5rem;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Navbar */
|
||||
.navbar {
|
||||
background-color: var(--primary-red);
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.navbar ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.navbar li {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.navbar a {
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.navbar a:hover, .navbar a.active {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 5px;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.email-management-container {
|
||||
flex: 1;
|
||||
padding: 30px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.email-management-container h1 {
|
||||
text-align: center;
|
||||
color: var(--primary-red);
|
||||
margin-bottom: 30px;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
/* Search Bar */
|
||||
.search-bar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-bar input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Grid Layout */
|
||||
.departments-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 25px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Department Card Style */
|
||||
.department-card {
|
||||
background-color: var(--white);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow);
|
||||
border-left: 4px solid var(--primary-red);
|
||||
padding: 20px;
|
||||
transition: var(--transition);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.department-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.department-card h3 {
|
||||
margin-top: 0;
|
||||
color: var(--primary-red);
|
||||
font-size: 1.1rem;
|
||||
border-bottom: 1px solid var(--medium-gray);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.department-card ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.department-card li {
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid var(--medium-gray);
|
||||
color: var(--dark-gray);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
.department-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.edit-btn, .delete-btn, .add-department-btn, .save-btn, .cancel-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.add-department-btn {
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Edit Form */
|
||||
.edit-form {
|
||||
background-color: var(--light-gray);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--medium-gray);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.email-input-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.email-input-group input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.remove-email {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #f44336;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.add-email {
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
background-color: var(--primary-red);
|
||||
color: var(--white);
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.terms {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.terms a {
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer-image {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 1024px) {
|
||||
.departments-list {
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header-bar {
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.college-name {
|
||||
margin: 10px 0;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.navbar li {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.email-management-container {
|
||||
padding: 20px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.departments-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
281
frontend/src/Page6.js
Normal file
281
frontend/src/Page6.js
Normal file
@@ -0,0 +1,281 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FaEdit, FaTrash, FaPlus, FaSave, FaTimes } from 'react-icons/fa';
|
||||
import './Page6.css';
|
||||
import image from './components/image.png';
|
||||
import somaiyatrust from './components/somaiyatrust.png';
|
||||
import redlines from './components/redlines.png';
|
||||
|
||||
const Page6 = () => {
|
||||
const [departments, setDepartments] = useState([]);
|
||||
const [programs, setPrograms] = useState([]);
|
||||
const [newDepartment, setNewDepartment] = useState('');
|
||||
const [newEmails, setNewEmails] = useState(['']);
|
||||
const [selectedProgram, setSelectedProgram] = useState('');
|
||||
const [editingDepartment, setEditingDepartment] = useState(null);
|
||||
const [isAddingNew, setIsAddingNew] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
fetchDepartments();
|
||||
fetchPrograms();
|
||||
}, []);
|
||||
|
||||
const fetchDepartments = async () => {
|
||||
try {
|
||||
const response = await axios.get('http://localhost:5000/api/department-emails');
|
||||
setDepartments(response.data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching departments:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchPrograms = async () => {
|
||||
try {
|
||||
const response = await axios.get('http://localhost:5000/api/programs');
|
||||
setPrograms(response.data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching programs:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddEmail = () => {
|
||||
setNewEmails([...newEmails, '']);
|
||||
};
|
||||
|
||||
const handleNewEmailChange = (index, value) => {
|
||||
const updatedEmails = [...newEmails];
|
||||
updatedEmails[index] = value;
|
||||
setNewEmails(updatedEmails);
|
||||
};
|
||||
|
||||
const handleRemoveEmail = (index) => {
|
||||
const updatedEmails = newEmails.filter((_, i) => i !== index);
|
||||
setNewEmails(updatedEmails);
|
||||
};
|
||||
|
||||
const startEditing = (department) => {
|
||||
setEditingDepartment(department);
|
||||
setNewEmails([...department.emails]);
|
||||
setIsAddingNew(false);
|
||||
};
|
||||
|
||||
const cancelEditing = () => {
|
||||
setEditingDepartment(null);
|
||||
setIsAddingNew(false);
|
||||
setNewDepartment('');
|
||||
setNewEmails(['']);
|
||||
setSelectedProgram('');
|
||||
};
|
||||
|
||||
const saveDepartment = async () => {
|
||||
try {
|
||||
const emailsToSave = newEmails.filter(email => email.trim() !== '');
|
||||
|
||||
if (emailsToSave.length === 0) {
|
||||
alert('At least one email is required');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAddingNew) {
|
||||
if (!newDepartment.trim()) {
|
||||
alert('Department name is required');
|
||||
return;
|
||||
}
|
||||
if (!selectedProgram) {
|
||||
alert('Please select a program');
|
||||
return;
|
||||
}
|
||||
|
||||
await axios.post('http://localhost:5000/api/department-emails', {
|
||||
department: newDepartment,
|
||||
emails: emailsToSave,
|
||||
program: selectedProgram,
|
||||
});
|
||||
} else {
|
||||
await axios.put(`http://localhost:5000/api/department-emails/${editingDepartment.department}`, {
|
||||
emails: emailsToSave
|
||||
});
|
||||
}
|
||||
|
||||
fetchDepartments();
|
||||
cancelEditing();
|
||||
} catch (error) {
|
||||
console.error('Error saving department:', error);
|
||||
alert('Error saving department. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
const deleteDepartment = async (departmentName) => {
|
||||
if (window.confirm(`Are you sure you want to delete ${departmentName}?`)) {
|
||||
try {
|
||||
await axios.delete(`http://localhost:5000/api/department-emails/${departmentName}`);
|
||||
fetchDepartments();
|
||||
} catch (error) {
|
||||
console.error('Error deleting department:', error);
|
||||
alert('Error deleting department. Please try again.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const filteredDepartments = departments.filter(dept =>
|
||||
dept.department.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="page-container">
|
||||
{/* Header Bar */}
|
||||
<header className="header-bar">
|
||||
<img src={image} alt="Left Logo" className="leftlogo" />
|
||||
<h1 className="college-name">Somaiya Scheduler</h1>
|
||||
<img src={somaiyatrust} alt="Right Logo" className="rightlogo" />
|
||||
</header>
|
||||
|
||||
{/* Navigation Bar */}
|
||||
<nav className="navbar">
|
||||
<ul>
|
||||
<li><Link to="/page2">Home</Link></li>
|
||||
<li><Link to="/page3">Schedule</Link></li>
|
||||
<li><Link to="/page5">Meeting DataBase</Link></li>
|
||||
<li><Link to="/page6">Manage Emails</Link></li>
|
||||
<li><a href="#">Contact</a></li>
|
||||
<li><Link to="/">Log Out</Link></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="email-management-container">
|
||||
<h1>Department Email Management</h1>
|
||||
|
||||
<div className="search-bar">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search departments..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{editingDepartment || isAddingNew ? (
|
||||
<div className="edit-form">
|
||||
<h2>{isAddingNew ? 'Add New Department' : `Editing: ${editingDepartment.department}`}</h2>
|
||||
|
||||
{isAddingNew && (
|
||||
<>
|
||||
<div className="form-group">
|
||||
<label>Program:</label>
|
||||
<select
|
||||
value={selectedProgram}
|
||||
onChange={(e) => setSelectedProgram(e.target.value)}
|
||||
>
|
||||
<option value="">Select Program</option>
|
||||
{programs.map((prog, idx) => (
|
||||
<option key={idx} value={prog.name}>{prog.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Department Name:</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newDepartment}
|
||||
onChange={(e) => setNewDepartment(e.target.value)}
|
||||
placeholder="Enter department name"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="form-group">
|
||||
<label>Emails:</label>
|
||||
{newEmails.map((email, index) => (
|
||||
<div key={index} className="email-input-group">
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => handleNewEmailChange(index, e.target.value)}
|
||||
placeholder="Enter email"
|
||||
/>
|
||||
{newEmails.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
className="remove-email"
|
||||
onClick={() => handleRemoveEmail(index)}
|
||||
>
|
||||
<FaTimes />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className="add-email"
|
||||
onClick={handleAddEmail}
|
||||
>
|
||||
<FaPlus /> Add Email
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="form-actions">
|
||||
<button type="button" className="save-btn" onClick={saveDepartment}>
|
||||
<FaSave /> Save
|
||||
</button>
|
||||
<button type="button" className="cancel-btn" onClick={cancelEditing}>
|
||||
<FaTimes /> Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
className="add-department-btn"
|
||||
onClick={() => {
|
||||
setIsAddingNew(true);
|
||||
setEditingDepartment(null);
|
||||
}}
|
||||
>
|
||||
<FaPlus /> Add New Department
|
||||
</button>
|
||||
|
||||
<div className="departments-list">
|
||||
{filteredDepartments.length === 0 ? (
|
||||
<p>No departments found</p>
|
||||
) : (
|
||||
filteredDepartments.map((dept) => (
|
||||
<div key={dept._id} className="department-card">
|
||||
<h3>{dept.department}</h3>
|
||||
<ul>
|
||||
{dept.emails.map((email, idx) => (
|
||||
<li key={idx}>{email}</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="department-actions">
|
||||
<button onClick={() => startEditing(dept)} className="edit-btn">
|
||||
<FaEdit /> Edit
|
||||
</button>
|
||||
<button onClick={() => deleteDepartment(dept.department)} className="delete-btn">
|
||||
<FaTrash /> Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<footer>
|
||||
<div className="terms">
|
||||
<a href="#">Terms and Policies</a>
|
||||
<img src={redlines} alt="Footer Logo" className="footer-image" />
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page6;
|
||||
Reference in New Issue
Block a user