diff --git a/client/src/App.js b/client/src/App.js index 8dbbb71..e215dbe 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -11,6 +11,7 @@ import ResetPwPage from "./Pages/ResetPw"; import FilterPage from "./Pages/FilterPage"; import WelcomeWithFilter from "./Pages/WelcomeWithFilter"; import "react-toastify/dist/ReactToastify.css"; +import CourseTable from "./Pages/CourseTable"; function App() { return ( @@ -26,6 +27,7 @@ function App() { }> }> } /> + } /> ); diff --git a/client/src/Pages/CourseForm.css b/client/src/Pages/CourseForm.css index ee4dd20..1d45e17 100644 --- a/client/src/Pages/CourseForm.css +++ b/client/src/Pages/CourseForm.css @@ -1,63 +1,85 @@ +/* CourseForm.css */ .form-container { - max-width: 600px; - margin: 30px auto; - padding: 20px; - background-color: #f4f4f9; - border-radius: 10px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + max-width: 600px; + margin: 50px auto; + padding: 20px; + border: 1px solid #ccc; + border-radius: 10px; + background-color: #f9f9f9; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +h2 { + text-align: center; + color: #333; + margin-bottom: 20px; + font-size: 1.8rem; +} + +form { + display: flex; + flex-direction: column; + gap: 15px; +} + +label { + font-size: 1rem; + font-weight: bold; + color: #555; + display: flex; + flex-direction: column; +} + +input[type="text"] { + padding: 10px; + font-size: 1rem; + border: 1px solid #ccc; + border-radius: 5px; + background-color: #fff; + transition: border-color 0.3s ease; +} + +input[type="text"]:focus { + border-color: #007bff; + outline: none; +} + +button { + padding: 10px 15px; + font-size: 1rem; + font-weight: bold; + color: #fff; + background-color: #007bff; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +button:hover { + background-color: #0056b3; +} + +button:active { + background-color: #003d7a; +} + +button[type="submit"] { + align-self: center; + width: 50%; +} + +@media (max-width: 768px) { + .form-container { + margin: 20px; + padding: 15px; } - + h2 { - text-align: center; - font-size: 24px; - color: #333; - margin-bottom: 20px; + font-size: 1.5rem; } - - form { - display: flex; - flex-direction: column; - gap: 20px; - } - - label { - font-size: 16px; - color: #333; - margin-bottom: 5px; - } - - input { - padding: 12px; - font-size: 16px; - border: 1px solid #ddd; - border-radius: 5px; + + button[type="submit"] { width: 100%; - box-sizing: border-box; } - - input:focus { - outline: none; - border-color: #4caf50; - box-shadow: 0 0 5px rgba(0, 192, 0, 0.2); - } - - button { - padding: 12px; - font-size: 16px; - background-color: #4caf50; - color: white; - border: none; - border-radius: 5px; - cursor: pointer; - width: 100%; - box-sizing: border-box; - } - - button:hover { - background-color: #45a049; - } - - button:focus { - outline: none; - } - \ No newline at end of file +} diff --git a/client/src/Pages/CourseForm.jsx b/client/src/Pages/CourseForm.jsx index 24e080c..8ace412 100644 --- a/client/src/Pages/CourseForm.jsx +++ b/client/src/Pages/CourseForm.jsx @@ -1,27 +1,182 @@ -import React from "react"; -import { useParams } from "react-router-dom"; +import React, { useState, useEffect } from "react"; +import { useLocation, useParams, useNavigate } from "react-router-dom"; import "./CourseForm.css"; const CourseForm = () => { const { id } = useParams(); // Get the course ID from the URL params + const location = useLocation(); + const navigate = useNavigate(); // Updated for navigation + const { course } = location.state || {}; + + const [options, setOptions] = useState({ + assessment: [], + reassessment: [], + paperSetting: [], + moderation: [], + pwdPaperSetter: [], + oralsPracticals: [], // New field for Orals/Practicals + }); + + const [formData, setFormData] = useState({ + assessment: "", + reassessment: "", + paperSetting: "", + moderation: "", + pwdPaperSetter: "", + oralsPracticals: "", // New field for Orals/Practicals + }); + + const [errors, setErrors] = useState({}); // To track validation errors + + // Fetch data for search bars + useEffect(() => { + const fetchOptions = async () => { + try { + const response = await fetch("/api/options"); // Replace with your API endpoint + const data = await response.json(); + setOptions(data); + } catch (error) { + console.error("Failed to fetch options:", error); + } + }; + + fetchOptions(); + }, []); + + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData({ ...formData, [name]: value }); + }; + + const validateForm = () => { + const newErrors = {}; + Object.keys(formData).forEach((field) => { + if (!formData[field]) { + newErrors[field] = "This field is required"; + } + }); + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSubmit = (e) => { + e.preventDefault(); + if (validateForm()) { + console.log("Form submitted:", formData); + + navigate("/courses", { state: { updatedCourse: { ...course, status: "Submitted" } } }); + } + }; return (

Course Info

-
+ -
); diff --git a/client/src/Pages/CourseTable.css b/client/src/Pages/CourseTable.css new file mode 100644 index 0000000..863b9d5 --- /dev/null +++ b/client/src/Pages/CourseTable.css @@ -0,0 +1,111 @@ +/* CourseTable.css */ + +.course-table { + width: 80%; + margin: 0 auto; + padding: 20px; + background-color: #f9f9f9; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + } + + .course-table h2 { + text-align: center; + margin-bottom: 20px; + color: #333; + } + + .course-table ul { + list-style-type: none; + padding: 0; + margin: 0; + } + + .course-table li { + padding: 10px; + margin-bottom: 10px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + display: flex; + justify-content: space-between; + align-items: center; + } + + .course-table li:last-child { + margin-bottom: 0; + } + + .course-table li .status { + font-size: 0.9rem; + padding: 5px 10px; + border-radius: 3px; + color: #fff; + } + + .course-table li .status.Pending { + background-color: #f39c12; + } + + .course-table li .status.Submitted { + background-color: #2ecc71; + } + + .course-table .form-container { + padding: 20px; + background-color: #fff; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + } + + .course-table .form-container h2 { + margin-bottom: 20px; + text-align: center; + } + + .course-table .form-container form { + display: flex; + flex-direction: column; + } + + .course-table .form-container label { + margin-bottom: 10px; + } + + .course-table .form-container input[type="text"] { + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + width: 100%; + } + + .course-table .form-container input[type="text"]:focus { + border-color: #007bff; + } + + .course-table .form-container .error { + border-color: red; + } + + .course-table .form-container .error-message { + color: red; + font-size: 0.9rem; + margin-top: 5px; + } + + .course-table .form-container button { + padding: 10px; + background-color: #007bff; + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 1rem; + margin-top: 15px; + } + + .course-table .form-container button:disabled { + background-color: #ccc; + cursor: not-allowed; + } + \ No newline at end of file diff --git a/client/src/Pages/CourseTable.jsx b/client/src/Pages/CourseTable.jsx new file mode 100644 index 0000000..8bf9a44 --- /dev/null +++ b/client/src/Pages/CourseTable.jsx @@ -0,0 +1,89 @@ +// import React from "react"; +// import { useNavigate } from "react-router-dom"; +// import "./CourseTable.css"; + +// const CourseTable = ({courses}) => { +// const navigate = useNavigate(); + +// const handleRowClick = (course) => { +// navigate(`/course-form/${course.id}`, { state: { course } }); +// }; + +// return ( +//
+//

Courses

+// +// +// +// +// +// +// +// +// +// {courses.map((course) => ( +// handleRowClick(course)} +// style={{ cursor: "pointer" }} +// > +// +// +// +// +// ))} +// +//
Course IDCourse NameStatus
{course.id}{course.name} +// +// {course.status} +// +//
+//
+// ); +// }; + +// export default CourseTable; + +import React, { useState, useEffect } from 'react'; +import { useLocation, useNavigate } from "react-router-dom"; +import "./CourseTable.css"; +import { fetchCourses } from '../api'; + +const CourseTable = () => { + const { state } = useLocation(); + const courses = state?.courses; + console.log(courses); + + useEffect(() => { + if (!courses || courses.length === 0) { + alert("No courses available"); + } + }, [courses]); + + if (!courses) { + return
Loading...
; + } + + return ( + + + + + + + + + + {courses.map(course => ( + + + + + + ))} + +
CourseIDCourse NameStatus
{course.courseId}{course.name}{course.status}
+ ); +} + +export default CourseTable; diff --git a/client/src/Pages/FilterPage.jsx b/client/src/Pages/FilterPage.jsx index c156a3b..88c56e5 100644 --- a/client/src/Pages/FilterPage.jsx +++ b/client/src/Pages/FilterPage.jsx @@ -1,67 +1,101 @@ import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; import "./FilterPage.css"; +import { fetchCourses } from "../api"; -const FilterPage = ({ onApplyFilter }) => { - const [selectedOption, setSelectedOption] = useState(null); +const FilterPage = () => { const [formData, setFormData] = useState({ scheme: "", - year: "", + semester: "", + department: "", + program: "", }); + const navigate = useNavigate(); + const handleInputChange = (e) => { const { name, value } = e.target; setFormData({ ...formData, [name]: value }); + + // Reset semester if program changes + if (name === "program") { + setFormData((prevData) => ({ ...prevData, semester: "" })); + } }; - const handleApplyFilter = () => { - if (!formData.scheme || !formData.year) { - alert("Please select both Scheme and Year before applying the filter."); + const handleApplyFilter = async () => { + if (!formData.scheme || !formData.semester || !formData.department || !formData.program) { + alert("Please fill all the fields before applying the filter."); return; } - if (!selectedOption) { - alert("Please select either 'Course' or 'Faculty' before applying the filter."); - return; + + try { + const filteredCourses = await fetchCourses(formData); + console.log(formData); + if (filteredCourses.length > 0) { + navigate("/courses", { state: { courses: filteredCourses } }); + } else { + alert("No courses found for the selected filters."); + } + } catch (error) { + console.error("Error fetching courses:", error); + alert("Failed to fetch courses. Please try again later."); } - onApplyFilter(selectedOption); // Pass the selected option to the parent }; - const handleSelectOption = (option) => { - setSelectedOption(option); + const getSemesters = () => { + if (!formData.program) return []; + if (formData.program === "B.Tech") { + return Array.from({ length: 8 }, (_, i) => i + 1); // 1 to 8 + } + if (formData.program === "M.Tech") { + return Array.from({ length: 4 }, (_, i) => i + 1); // 1 to 4 + } + return []; }; return (
+ + + - - -
diff --git a/client/src/api.js b/client/src/api.js new file mode 100644 index 0000000..2725849 --- /dev/null +++ b/client/src/api.js @@ -0,0 +1,28 @@ +const BASE_URL = "http://localhost:8080/api"; + +export const fetchCourses = async (filterData) => { + try { + + // Serialize filterData into query parameters + const queryString = new URLSearchParams(filterData).toString(); + // console.log(queryString); + const response = await fetch(`${BASE_URL}/courses?${queryString}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch courses"); + } + + const filteredCourses = await response.json(); + console.log(filteredCourses); + return filteredCourses; + } catch (error) { + console.error("Error fetching courses:", error); + throw error; // Re-throw error to be handled by the caller + } +}; + diff --git a/server/models/Appointment.js b/server/models/Appointment.js new file mode 100644 index 0000000..49c45fd --- /dev/null +++ b/server/models/Appointment.js @@ -0,0 +1,11 @@ +const mongoose = require("mongoose"); +const { v4: uuidv4 } = require("uuid"); // For UUID generation + +const AppointmentSchema = new mongoose.Schema({ + appointmentId: { type: String, default: uuidv4 }, // Auto-generate using UUID + courseId: { type: String, required: true, ref: "Course" }, + facultyId: { type: String, required: true, ref: "Faculty" }, + appointmentType: { type: [String], required: true }, // Array of appointment types +}); + +module.exports = mongoose.model("Appointment", AppointmentSchema); diff --git a/server/models/Course.js b/server/models/Course.js new file mode 100644 index 0000000..84f0b6e --- /dev/null +++ b/server/models/Course.js @@ -0,0 +1,13 @@ +const mongoose = require("mongoose"); + +const CourseSchema = new mongoose.Schema({ + courseId: { type: String, required: true, unique: true }, + name: { type: String, required: true }, + department: { type: String, required: true }, + program: { type: String, required: true }, + scheme: { type: String, required: true }, + semester: { type: Number, required: true }, + status: { type: String, enum: ["submitted", "not submitted"], default: "not submitted" }, +}); + +module.exports = mongoose.model("Course", CourseSchema); diff --git a/server/models/Faculty.js b/server/models/Faculty.js new file mode 100644 index 0000000..9e77a43 --- /dev/null +++ b/server/models/Faculty.js @@ -0,0 +1,11 @@ +const mongoose = require("mongoose"); + +const FacultySchema = new mongoose.Schema({ + facultyId: { type: String, required: true, unique: true }, + name: { type: String, required: true }, + email: { type: String, required: true }, + department: { type: String, required: true }, + program: { type: String, required: true }, +}); + +module.exports = mongoose.model("Faculty", FacultySchema); diff --git a/server/package-lock.json b/server/package-lock.json index 235d398..5ab6d3c 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -25,7 +25,8 @@ "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", "passport-local": "^1.0.0", - "passport-local-mongoose": "^8.0.0" + "passport-local-mongoose": "^8.0.0", + "uuid": "^11.0.3" }, "devDependencies": { "nodemon": "^3.1.0" @@ -712,6 +713,18 @@ "node": ">=14" } }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/gcp-metadata": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", @@ -898,6 +911,18 @@ "node": ">=14.0.0" } }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/gopd": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.1.0.tgz", @@ -2219,16 +2244,15 @@ } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", + "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], - "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/vary": { diff --git a/server/package.json b/server/package.json index 19285e6..53728c5 100644 --- a/server/package.json +++ b/server/package.json @@ -5,7 +5,7 @@ "main": "sever.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build" : "npm install --prefix ../client && npm run build --prefix ../client && npm install", + "build": "npm install --prefix ../client && npm run build --prefix ../client && npm install", "start": "nodemon server.js" }, "repository": { @@ -34,7 +34,8 @@ "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0", "passport-local": "^1.0.0", - "passport-local-mongoose": "^8.0.0" + "passport-local-mongoose": "^8.0.0", + "uuid": "^11.0.3" }, "devDependencies": { "nodemon": "^3.1.0" diff --git a/server/routes/appointmentRoutes.js b/server/routes/appointmentRoutes.js new file mode 100644 index 0000000..ffce999 --- /dev/null +++ b/server/routes/appointmentRoutes.js @@ -0,0 +1,51 @@ +const express = require("express"); +const Appointment = require("../models/Appointment"); + +const router = express.Router(); + +// Get all appointments +router.get("/", async (req, res) => { + try { + const appointments = await Appointment.find() + .populate("courseId", "name department") + .populate("facultyId", "name email"); + res.json(appointments); + } catch (error) { + res.status(500).json({ error: "Failed to fetch appointments" }); + } +}); + +// Create a new appointment +router.post("/", async (req, res) => { + try { + const { courseId, facultyId, appointmentType } = req.body; + + const newAppointment = new Appointment({ + courseId, + facultyId, + appointmentType, + }); + + await newAppointment.save(); + res.status(201).json(newAppointment); + } catch (error) { + res.status(400).json({ error: "Failed to create appointment" }); + } + }); + +// Get appointment by ID +router.get("/:id", async (req, res) => { + try { + const appointment = await Appointment.findOne({ appointmentId: req.params.id }) + .populate("courseId", "name department") + .populate("facultyId", "name email"); + if (!appointment) { + return res.status(404).json({ error: "Appointment not found" }); + } + res.json(appointment); + } catch (error) { + res.status(500).json({ error: "Failed to fetch appointment" }); + } +}); + +module.exports = router; diff --git a/server/routes/courseRoutes.js b/server/routes/courseRoutes.js new file mode 100644 index 0000000..a932537 --- /dev/null +++ b/server/routes/courseRoutes.js @@ -0,0 +1,38 @@ +const express = require("express"); +const Course = require("../models/Course"); + +const router = express.Router(); + +router.get("/", async (req, res) => { + try { + const filter = {}; + + if (req.query.department) filter.department = req.query.department; + if (req.query.program) filter.program = req.query.program; + if (req.query.semester) filter.semester = Number(req.query.semester); // Convert to number if needed + if (req.query.scheme) filter.scheme = req.query.scheme; + + const courses = await Course.find(filter); + res.json(courses); + } catch (error) { + console.error("Error fetching courses:", error); + res.status(500).json({ error: "Failed to fetch courses" }); + } + }); + + +// Get course by ID +router.get("/:id", async (req, res) => { + try { + const course = await Course.findOne({ courseId: req.params.id }); + if (!course) { + return res.status(404).json({ error: "Course not found" }); + } + res.json(course); + } catch (error) { + console.error("Error fetching course:", error); + res.status(500).json({ error: "Failed to fetch course" }); + } +}); + +module.exports = router; diff --git a/server/routes/facultyRoutes.js b/server/routes/facultyRoutes.js new file mode 100644 index 0000000..a7809b7 --- /dev/null +++ b/server/routes/facultyRoutes.js @@ -0,0 +1,29 @@ +const express = require("express"); +const Faculty = require("../models/Faculty"); + +const router = express.Router(); + +// Get all faculty members +router.get("/", async (req, res) => { + try { + const faculty = await Faculty.find(); + res.json(faculty); + } catch (error) { + res.status(500).json({ error: "Failed to fetch faculty members" }); + } +}); + +// Get faculty by ID +router.get("/:id", async (req, res) => { + try { + const faculty = await Faculty.findOne({ facultyId: req.params.id }); + if (!faculty) { + return res.status(404).json({ error: "Faculty member not found" }); + } + res.json(faculty); + } catch (error) { + res.status(500).json({ error: "Failed to fetch faculty member" }); + } +}); + +module.exports = router; diff --git a/server/server.js b/server/server.js index 7b1b9b4..834670e 100644 --- a/server/server.js +++ b/server/server.js @@ -1,25 +1,38 @@ const express = require("express"); -const User = require("./models/User"); +const mongoose = require("mongoose"); +const bodyParser = require("body-parser"); const cors = require("cors"); +require("dotenv").config(); const passport = require("passport"); const session = require("express-session"); -const bodyParser = require("body-parser"); const bcrypt = require("bcryptjs"); const LocalStrategy = require("passport-local").Strategy; -const PasswordRouter = require("./routes/authRoutes"); const crypto = require("crypto"); const jwt = require("jsonwebtoken"); const path = require("path"); +const User = require("./models/User"); +const PasswordRouter = require("./routes/authRoutes"); +const courseRoutes = require("./routes/courseRoutes"); +const facultyRoutes = require("./routes/facultyRoutes"); +const appointmentRoutes = require("./routes/appointmentRoutes"); + +// Existing Database Connection const { connectdb } = require("./ConnectionDb"); connectdb(); +// MongoDB Connection +mongoose + .connect(process.env.mongoURI) + .then(() => console.log("MongoDB connected")) + .catch((err) => console.error("MongoDB connection error:", err)); + const app = express(); // Middleware +app.use(cors()); app.use(express.json()); app.use(bodyParser.urlencoded({ extended: true })); - app.use( session({ secret: "secret", @@ -39,7 +52,7 @@ app.use( }) ); -// Passport configuration +// Passport Configuration require("./config/passport"); passport.use( @@ -76,7 +89,11 @@ passport.deserializeUser((id, done) => { // Routes app.use("/password", PasswordRouter); +app.use("/api/courses", courseRoutes); +app.use("/api/faculty", facultyRoutes); +app.use("/api/appointments", appointmentRoutes); +// OAuth Routes app.get( "/auth/google", passport.authenticate("google", { scope: ["profile", "email"] }) @@ -86,7 +103,7 @@ app.get( "/auth/google/callback", passport.authenticate("google", { failureRedirect: "/" }), function (req, res) { - res.redirect("http://localhost:3000/Welcom"); + res.redirect("http://localhost:3000/Welcom"); } ); @@ -174,15 +191,17 @@ app.get("/api/user/profile", async (req, res) => { } }); -// Serve static files +// Serve Static Files app.use(express.static(path.join(__dirname, "../client/build"))); -// Catch-all route to serve React app +// Catch-All Route app.get("*", (req, res) => res.sendFile(path.join(__dirname, "../client/build/index.html")) ); -const Port = 8080; + +// Start Server +const Port = process.env.PORT || 8080; app.listen(Port, () => { console.log(`Server is Running at port ${Port}`); });