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();
|
require("dotenv").config();
|
||||||
const express = require("express");
|
const express = require("express");
|
||||||
const mongoose = require("mongoose");
|
|
||||||
const bodyParser = require("body-parser");
|
|
||||||
const cors = require("cors");
|
const cors = require("cors");
|
||||||
const nodemailer = require("nodemailer");
|
const bodyParser = require("body-parser");
|
||||||
const multer = require("multer");
|
|
||||||
const path = require("path");
|
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();
|
const app = express();
|
||||||
|
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use(bodyParser.urlencoded({ extended: true }));
|
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
|
connectDB();
|
||||||
const storage = multer.diskStorage({
|
|
||||||
destination: "uploads/",
|
|
||||||
filename: (req, file, cb) => {
|
|
||||||
cb(null, Date.now() + path.extname(file.originalname));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const upload = multer({ storage });
|
|
||||||
|
|
||||||
// MongoDB connection
|
app.use("/api", emailRoutes);
|
||||||
mongoose
|
app.use("/api", meetingRoutes);
|
||||||
.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true })
|
app.use("/api", departmentRoutes);
|
||||||
.then(() => console.log("Connected to MongoDB"))
|
|
||||||
.catch((err) => console.error("MongoDB Connection Error:", err));
|
|
||||||
|
|
||||||
// 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;
|
const PORT = process.env.PORT || 5000;
|
||||||
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
|
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.
@@ -5,8 +5,10 @@ import Page2 from './Page2';
|
|||||||
import Page3 from './Page3';
|
import Page3 from './Page3';
|
||||||
import Page4 from './Page4';
|
import Page4 from './Page4';
|
||||||
import Page5 from './Page5';
|
import Page5 from './Page5';
|
||||||
|
import Page6 from './Page6';
|
||||||
import { GoogleOAuthProvider } from '@react-oauth/google';
|
import { GoogleOAuthProvider } from '@react-oauth/google';
|
||||||
|
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<GoogleOAuthProvider clientId="YOUR_GOOGLE_OAUTH_CLIENT_ID">
|
<GoogleOAuthProvider clientId="YOUR_GOOGLE_OAUTH_CLIENT_ID">
|
||||||
@@ -17,6 +19,7 @@ const App = () => {
|
|||||||
<Route path="/page3" element={<Page3 />} />
|
<Route path="/page3" element={<Page3 />} />
|
||||||
<Route path="/page4" element={<Page4 />} />
|
<Route path="/page4" element={<Page4 />} />
|
||||||
<Route path="/page5" element={<Page5 />} />
|
<Route path="/page5" element={<Page5 />} />
|
||||||
|
<Route path="/page6" element={<Page6 />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
</GoogleOAuthProvider>
|
</GoogleOAuthProvider>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/*Page1.css*/
|
||||||
|
|
||||||
/* General Styles */
|
/* General Styles */
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//Page1.js
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { GoogleLogin } from '@react-oauth/google'; // Import GoogleLogin component
|
import { GoogleLogin } from '@react-oauth/google'; // Import GoogleLogin component
|
||||||
@@ -31,7 +33,7 @@ const Page1 = () => {
|
|||||||
|
|
||||||
// Allowed credentials
|
// Allowed credentials
|
||||||
const allowedCredentials = [
|
const allowedCredentials = [
|
||||||
{ email: "hodcomp@somaiya.edu", password: "hodcomp123!" },
|
{ email: "hodcomp@somaiya.edu", password: "hodcomp123" },
|
||||||
{ email: "director.kjsse@somaiya.edu", password: "directorkjsse2024" },
|
{ email: "director.kjsse@somaiya.edu", password: "directorkjsse2024" },
|
||||||
{ email: "dean.engg@somaiya.edu", password: "deanengg2024" },
|
{ email: "dean.engg@somaiya.edu", password: "deanengg2024" },
|
||||||
{ email: "hodextc@somaiya.edu", password: "hodextc2024" },
|
{ email: "hodextc@somaiya.edu", password: "hodextc2024" },
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/*Page2.css*/
|
||||||
|
|
||||||
/* General Styles */
|
/* General Styles */
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//Page2.js
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import './Page2.css'; // CSS File import
|
import './Page2.css'; // CSS File import
|
||||||
@@ -30,7 +32,7 @@ const Page2 = () => {
|
|||||||
<li><Link to="/page2">Home</Link></li>
|
<li><Link to="/page2">Home</Link></li>
|
||||||
<li><a href="#">Schedule</a></li>
|
<li><a href="#">Schedule</a></li>
|
||||||
<li><Link to="/page5">Meeting DataBase</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>
|
<li><Link to="/">Log Out</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/* Page3.css */
|
/* Page3.css */
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//Page3.js
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import './Page3.css'
|
import './Page3.css'
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/*Page4.css*/
|
||||||
|
|
||||||
/* Container for the content area */
|
/* Container for the content area */
|
||||||
.content-area {
|
.content-area {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,28 +1,20 @@
|
|||||||
|
//Page4.js
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import './Page2.css';
|
import './Page2.css';
|
||||||
import './Page4.css'; // Import the same CSS file for consistent styling
|
import './Page4.css';
|
||||||
import { FaTimes } from "react-icons/fa"; // Red cross icon for agenda/removal
|
import { FaTimes } from "react-icons/fa";
|
||||||
import image from './components/image.png';
|
import image from './components/image.png';
|
||||||
import somaiyatrust from './components/somaiyatrust.png';
|
import somaiyatrust from './components/somaiyatrust.png';
|
||||||
import redlines from './components/redlines.png';
|
import redlines from './components/redlines.png';
|
||||||
|
import { useEffect } from "react";
|
||||||
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",
|
|
||||||
];
|
|
||||||
|
|
||||||
const Page4 = () => {
|
const Page4 = () => {
|
||||||
|
const [programs, setPrograms] = useState([]);
|
||||||
|
const [departments, setDepartments] = useState([]);
|
||||||
|
const [departmentEmails, setDepartmentEmails] = useState([]);
|
||||||
const [program, setProgram] = useState("");
|
const [program, setProgram] = useState("");
|
||||||
const [department, setDepartment] = useState("");
|
const [department, setDepartment] = useState("");
|
||||||
const [subject, setSubject] = useState("");
|
const [subject, setSubject] = useState("");
|
||||||
@@ -34,6 +26,33 @@ const Page4 = () => {
|
|||||||
const [recipients, setRecipients] = useState([""]);
|
const [recipients, setRecipients] = useState([""]);
|
||||||
const [attachments, setAttachments] = 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 addAgenda = () => setAgenda([...agenda, ""]);
|
||||||
const handleAgendaChange = (index, value) => {
|
const handleAgendaChange = (index, value) => {
|
||||||
const updatedAgenda = [...agenda];
|
const updatedAgenda = [...agenda];
|
||||||
@@ -86,7 +105,7 @@ const Page4 = () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.response?.status === 409) {
|
if (err.response?.status === 409) {
|
||||||
const conflictDetails = err.response.data.conflictDetails;
|
const conflictDetails = err.response.data.conflictDetails;
|
||||||
alert(`Conflict Detected:
|
alert(`Conflict Detected :
|
||||||
Department: ${conflictDetails.department}
|
Department: ${conflictDetails.department}
|
||||||
Date: ${conflictDetails.date}
|
Date: ${conflictDetails.date}
|
||||||
Time: ${conflictDetails.startTime} - ${conflictDetails.endTime}
|
Time: ${conflictDetails.startTime} - ${conflictDetails.endTime}
|
||||||
@@ -114,7 +133,7 @@ const Page4 = () => {
|
|||||||
<li><Link to="/page2">Home</Link></li>
|
<li><Link to="/page2">Home</Link></li>
|
||||||
<li><Link to="/page3">Schedule</Link></li>
|
<li><Link to="/page3">Schedule</Link></li>
|
||||||
<li><Link to="/page5">Meeting DataBase</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>
|
<li><Link to="/">Log Out</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -134,13 +153,11 @@ const Page4 = () => {
|
|||||||
{/* Content Area (Right Side) */}
|
{/* Content Area (Right Side) */}
|
||||||
<div className="content-area">
|
<div className="content-area">
|
||||||
<h1>BOS Email Management System</h1>
|
<h1>BOS Email Management System</h1>
|
||||||
<label>Program:</label>
|
<label>Programme:</label>
|
||||||
<select onChange={(e) => setProgram(e.target.value)} value={program}>
|
<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) => (
|
{programs.map((prog, idx) => (
|
||||||
<option key={idx} value={prog}>
|
<option key={idx} value={prog.name}>{prog.name}</option>
|
||||||
{prog}
|
|
||||||
</option>
|
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|||||||
@@ -1,141 +1,309 @@
|
|||||||
/* Ensure body has margin 0 and full height for scrolling */
|
/*Page5.css*/
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
min-height: 100vh;
|
|
||||||
overflow-y: auto; /* Ensure scrolling */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main container for the meetings */
|
/* Base Styles */
|
||||||
.meetings-container {
|
:root {
|
||||||
background-color: #ffffff;
|
--primary-red: #B7202E;
|
||||||
border-radius: 10px; /* Rounded corners */
|
--dark-red: #8a0000;
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
--light-red: #d32f2f;
|
||||||
border: 1px solid #e0e0e0; /* Light border for the box */
|
--white: #ffffff;
|
||||||
box-sizing: border-box;
|
--light-gray: #f5f5f5;
|
||||||
max-width: 1200px;
|
--medium-gray: #e0e0e0;
|
||||||
margin: 20px auto; /* Added margin to ensure some space from top */
|
--dark-gray: #757575;
|
||||||
padding: 20px;
|
--black: #212121;
|
||||||
}
|
--shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
--transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
/* Heading for the page */
|
/* Page Container */
|
||||||
h1 {
|
.page-container {
|
||||||
text-align: center;
|
display: flex;
|
||||||
font-size: 2rem;
|
flex-direction: column;
|
||||||
margin-bottom: 20px;
|
min-height: 100vh;
|
||||||
}
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
/* List of meetings */
|
html, body {
|
||||||
.meetings-list {
|
margin: 0;
|
||||||
display: flex;
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header-bar {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 20px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styling for each meeting box */
|
.college-name {
|
||||||
.meeting-box {
|
margin: 10px 0;
|
||||||
background-color: #ffffff;
|
font-size: 1.2rem;
|
||||||
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 */
|
.navbar li {
|
||||||
.meeting-box h2 {
|
padding: 10px 15px;
|
||||||
font-size: 1.5rem;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styling for meeting details like Program, Date, etc. */
|
.meetings-page-container {
|
||||||
.meeting-box p {
|
padding: 20px 15px;
|
||||||
font-size: 1rem;
|
}
|
||||||
color: #555;
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.meetings-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styling for links like Google Calendar and attachments */
|
.detail-row {
|
||||||
.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;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.meeting-box ul {
|
.detail-label {
|
||||||
list-style-type: none;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meeting-box ul li {
|
|
||||||
font-size: 1rem;
|
|
||||||
color: #444;
|
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive Design for smaller screens */
|
.meeting-meta {
|
||||||
@media (max-width: 768px) {
|
flex-direction: column;
|
||||||
.meeting-box {
|
gap: 5px;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.google-calendar-link:hover {
|
|
||||||
background-color: #9e1c25; /* Darkened shade of red on hover */
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,81 +1,162 @@
|
|||||||
|
//Page5.js
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import './Page5.css';
|
import './Page5.css';
|
||||||
|
import somaiyatrust from './components/somaiyatrust.png';
|
||||||
|
import image from './components/image.png';
|
||||||
|
import redlines from './components/redlines.png';
|
||||||
|
|
||||||
const Page5 = () => {
|
const Page5 = () => {
|
||||||
const [meetings, setMeetings] = useState([]);
|
const [meetings, setMeetings] = useState([]);
|
||||||
|
const [expandedIndex, setExpandedIndex] = useState(null);
|
||||||
|
|
||||||
// Fetch meetings data from the server
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
axios.get('http://localhost:5000/api/meetings') // Updated to backend URL
|
axios.get('http://localhost:5000/api/meetings')
|
||||||
.then((response) => {
|
.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) => {
|
.catch((error) => {
|
||||||
console.error('Error fetching meetings:', error);
|
console.error('Error fetching meetings:', error);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const toggleExpand = (index) => {
|
||||||
|
setExpandedIndex(prev => (prev === index ? null : index));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="meetings-container">
|
<div className="page-container">
|
||||||
<h1>Meeting Details</h1>
|
{/* Header Bar */}
|
||||||
<div className="meetings-list">
|
<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 ? (
|
{meetings.length === 0 ? (
|
||||||
<p>No meetings available</p>
|
<p className="no-meetings">No meetings available</p>
|
||||||
) : (
|
) : (
|
||||||
meetings.map((meeting, index) => (
|
<div className="meetings-grid-container">
|
||||||
<div key={index} className="meeting-box">
|
<div className="meetings-grid">
|
||||||
<h2>{meeting.subject}</h2>
|
{meetings.map((meeting, index) => (
|
||||||
<p><strong>Program:</strong> {meeting.program}</p>
|
<div
|
||||||
<p><strong>Department:</strong> {meeting.department}</p>
|
key={index}
|
||||||
<p><strong>Date:</strong> {meeting.date}</p>
|
className={`meeting-card ${expandedIndex === index ? 'expanded' : ''}`}
|
||||||
<p><strong>Time:</strong> {meeting.startTime} - {meeting.endTime}</p>
|
>
|
||||||
|
<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>
|
{expandedIndex === index && (
|
||||||
{meeting.agenda && meeting.agenda.length > 0 ? (
|
<div className="card-details">
|
||||||
<ul>
|
<div className="detail-row">
|
||||||
{meeting.agenda.map((item, idx) => (
|
<span className="detail-label">Program:</span>
|
||||||
<li key={idx}>{item}</li>
|
<span>{meeting.program}</span>
|
||||||
))}
|
</div>
|
||||||
</ul>
|
|
||||||
) : (
|
|
||||||
<p>No agenda provided</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<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 */}
|
<div className="detail-section">
|
||||||
<p><strong>Attachments:</strong></p>
|
<h3>Recipients</h3>
|
||||||
{meeting.attachments && meeting.attachments.length > 0 ? (
|
<div className="recipients-list">
|
||||||
<ul>
|
{meeting.recipients.join(', ')}
|
||||||
{meeting.attachments.map((file, idx) => (
|
</div>
|
||||||
<li key={idx}>
|
</div>
|
||||||
<a href={`/uploads/${file.filename}`} target="_blank" rel="noopener noreferrer">
|
|
||||||
{file.filename}
|
<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>
|
</a>
|
||||||
</li>
|
</div>
|
||||||
))}
|
)}
|
||||||
</ul>
|
</div>
|
||||||
) : (
|
))}
|
||||||
<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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
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