Added backend and integration for FilterPage and CourseTable done
This commit is contained in:
@@ -11,6 +11,7 @@ import ResetPwPage from "./Pages/ResetPw";
|
|||||||
import FilterPage from "./Pages/FilterPage";
|
import FilterPage from "./Pages/FilterPage";
|
||||||
import WelcomeWithFilter from "./Pages/WelcomeWithFilter";
|
import WelcomeWithFilter from "./Pages/WelcomeWithFilter";
|
||||||
import "react-toastify/dist/ReactToastify.css";
|
import "react-toastify/dist/ReactToastify.css";
|
||||||
|
import CourseTable from "./Pages/CourseTable";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -26,6 +27,7 @@ function App() {
|
|||||||
<Route path="/ForgetPw" element={<ForgetPwPage />}></Route>
|
<Route path="/ForgetPw" element={<ForgetPwPage />}></Route>
|
||||||
<Route path="/ResetPw/:token" element={<ResetPwPage />}></Route>
|
<Route path="/ResetPw/:token" element={<ResetPwPage />}></Route>
|
||||||
<Route path="/Filter" element={<FilterPage />} />
|
<Route path="/Filter" element={<FilterPage />} />
|
||||||
|
<Route path="/courses" element={<CourseTable />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,63 +1,85 @@
|
|||||||
|
/* CourseForm.css */
|
||||||
.form-container {
|
.form-container {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: 30px auto;
|
margin: 50px auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background-color: #f4f4f9;
|
border: 1px solid #ccc;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
background-color: #f9f9f9;
|
||||||
}
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 24px;
|
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
form {
|
form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 20px;
|
gap: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font-size: 16px;
|
font-size: 1rem;
|
||||||
color: #333;
|
font-weight: bold;
|
||||||
margin-bottom: 5px;
|
color: #555;
|
||||||
}
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
input {
|
input[type="text"] {
|
||||||
padding: 12px;
|
padding: 10px;
|
||||||
font-size: 16px;
|
font-size: 1rem;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ccc;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
width: 100%;
|
background-color: #fff;
|
||||||
box-sizing: border-box;
|
transition: border-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:focus {
|
input[type="text"]:focus {
|
||||||
|
border-color: #007bff;
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #4caf50;
|
}
|
||||||
box-shadow: 0 0 5px rgba(0, 192, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
padding: 12px;
|
padding: 10px 15px;
|
||||||
font-size: 16px;
|
font-size: 1rem;
|
||||||
background-color: #4caf50;
|
font-weight: bold;
|
||||||
color: white;
|
color: #fff;
|
||||||
|
background-color: #007bff;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
cursor: pointer;
|
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 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[type="submit"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
button:hover {
|
|
||||||
background-color: #45a049;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,182 @@
|
|||||||
import React from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useLocation, useParams, useNavigate } from "react-router-dom";
|
||||||
import "./CourseForm.css";
|
import "./CourseForm.css";
|
||||||
|
|
||||||
const CourseForm = () => {
|
const CourseForm = () => {
|
||||||
const { id } = useParams(); // Get the course ID from the URL params
|
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 (
|
return (
|
||||||
<div className="form-container">
|
<div className="form-container">
|
||||||
<h2>Course Info</h2>
|
<h2>Course Info</h2>
|
||||||
<form>
|
<form onSubmit={handleSubmit}>
|
||||||
<label>
|
<label>
|
||||||
Course ID:
|
Course ID:
|
||||||
<input type="text" value={id} readOnly />
|
<input type="text" value={course?.id || id} readOnly />
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Course Name:
|
Course Name:
|
||||||
<input type="text" placeholder="Enter course name" />
|
<input type="text" value={course?.name || ""} readOnly />
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label className={errors.oralsPracticals ? "error" : ""}>
|
||||||
Status:
|
Orals/Practicals:
|
||||||
<input type="text" placeholder="Enter course status" />
|
<input
|
||||||
|
type="text"
|
||||||
|
name="oralsPracticals"
|
||||||
|
list="oralsPracticals-options"
|
||||||
|
value={formData.oralsPracticals}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<datalist id="oralsPracticals-options">
|
||||||
|
{options.oralsPracticals.map((option, index) => (
|
||||||
|
<option key={index} value={option} />
|
||||||
|
))}
|
||||||
|
</datalist>
|
||||||
|
{errors.oralsPracticals && <span className="error-message">{errors.oralsPracticals}</span>}
|
||||||
</label>
|
</label>
|
||||||
<button type="submit">Submit</button>
|
<label className={errors.assessment ? "error" : ""}>
|
||||||
|
Assessment:
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="assessment"
|
||||||
|
list="assessment-options"
|
||||||
|
value={formData.assessment}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<datalist id="assessment-options">
|
||||||
|
{options.assessment.map((option, index) => (
|
||||||
|
<option key={index} value={option} />
|
||||||
|
))}
|
||||||
|
</datalist>
|
||||||
|
{errors.assessment && <span className="error-message">{errors.assessment}</span>}
|
||||||
|
</label>
|
||||||
|
<label className={errors.reassessment ? "error" : ""}>
|
||||||
|
Reassessment:
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="reassessment"
|
||||||
|
list="reassessment-options"
|
||||||
|
value={formData.reassessment}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<datalist id="reassessment-options">
|
||||||
|
{options.reassessment.map((option, index) => (
|
||||||
|
<option key={index} value={option} />
|
||||||
|
))}
|
||||||
|
</datalist>
|
||||||
|
{errors.reassessment && <span className="error-message">{errors.reassessment}</span>}
|
||||||
|
</label>
|
||||||
|
<label className={errors.paperSetting ? "error" : ""}>
|
||||||
|
Paper Setting:
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="paperSetting"
|
||||||
|
list="paperSetting-options"
|
||||||
|
value={formData.paperSetting}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<datalist id="paperSetting-options">
|
||||||
|
{options.paperSetting.map((option, index) => (
|
||||||
|
<option key={index} value={option} />
|
||||||
|
))}
|
||||||
|
</datalist>
|
||||||
|
{errors.paperSetting && <span className="error-message">{errors.paperSetting}</span>}
|
||||||
|
</label>
|
||||||
|
<label className={errors.moderation ? "error" : ""}>
|
||||||
|
Moderation:
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="moderation"
|
||||||
|
list="moderation-options"
|
||||||
|
value={formData.moderation}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<datalist id="moderation-options">
|
||||||
|
{options.moderation.map((option, index) => (
|
||||||
|
<option key={index} value={option} />
|
||||||
|
))}
|
||||||
|
</datalist>
|
||||||
|
{errors.moderation && <span className="error-message">{errors.moderation}</span>}
|
||||||
|
</label>
|
||||||
|
<label className={errors.pwdPaperSetter ? "error" : ""}>
|
||||||
|
PwD Paper Setter:
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="pwdPaperSetter"
|
||||||
|
list="pwdPaperSetter-options"
|
||||||
|
value={formData.pwdPaperSetter}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
/>
|
||||||
|
<datalist id="pwdPaperSetter-options">
|
||||||
|
{options.pwdPaperSetter.map((option, index) => (
|
||||||
|
<option key={index} value={option} />
|
||||||
|
))}
|
||||||
|
</datalist>
|
||||||
|
{errors.pwdPaperSetter && <span className="error-message">{errors.pwdPaperSetter}</span>}
|
||||||
|
</label>
|
||||||
|
<button type="submit" disabled={Object.keys(errors).length > 0}>Submit</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
111
client/src/Pages/CourseTable.css
Normal file
111
client/src/Pages/CourseTable.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
89
client/src/Pages/CourseTable.jsx
Normal file
89
client/src/Pages/CourseTable.jsx
Normal file
@@ -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 (
|
||||||
|
// <div className="course-table">
|
||||||
|
// <h1>Courses</h1>
|
||||||
|
// <table border="1">
|
||||||
|
// <thead>
|
||||||
|
// <tr>
|
||||||
|
// <th>Course ID</th>
|
||||||
|
// <th>Course Name</th>
|
||||||
|
// <th>Status</th>
|
||||||
|
// </tr>
|
||||||
|
// </thead>
|
||||||
|
// <tbody>
|
||||||
|
// {courses.map((course) => (
|
||||||
|
// <tr
|
||||||
|
// key={course.id}
|
||||||
|
// onClick={() => handleRowClick(course)}
|
||||||
|
// style={{ cursor: "pointer" }}
|
||||||
|
// >
|
||||||
|
// <td>{course.id}</td>
|
||||||
|
// <td>{course.name}</td>
|
||||||
|
// <td>
|
||||||
|
// <span className={`status ${course.status.replace(" ", "-")}`}>
|
||||||
|
// {course.status}
|
||||||
|
// </span>
|
||||||
|
// </td>
|
||||||
|
// </tr>
|
||||||
|
// ))}
|
||||||
|
// </tbody>
|
||||||
|
// </table>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// 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 <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table className="course-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>CourseID</th>
|
||||||
|
<th>Course Name</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{courses.map(course => (
|
||||||
|
<tr key={course.id}>
|
||||||
|
<td>{course.courseId}</td>
|
||||||
|
<td>{course.name}</td>
|
||||||
|
<td>{course.status}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CourseTable;
|
||||||
@@ -1,67 +1,101 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
import "./FilterPage.css";
|
import "./FilterPage.css";
|
||||||
|
import { fetchCourses } from "../api";
|
||||||
|
|
||||||
const FilterPage = ({ onApplyFilter }) => {
|
const FilterPage = () => {
|
||||||
const [selectedOption, setSelectedOption] = useState(null);
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
scheme: "",
|
scheme: "",
|
||||||
year: "",
|
semester: "",
|
||||||
|
department: "",
|
||||||
|
program: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleInputChange = (e) => {
|
const handleInputChange = (e) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setFormData({ ...formData, [name]: value });
|
setFormData({ ...formData, [name]: value });
|
||||||
|
|
||||||
|
// Reset semester if program changes
|
||||||
|
if (name === "program") {
|
||||||
|
setFormData((prevData) => ({ ...prevData, semester: "" }));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleApplyFilter = () => {
|
const handleApplyFilter = async () => {
|
||||||
if (!formData.scheme || !formData.year) {
|
if (!formData.scheme || !formData.semester || !formData.department || !formData.program) {
|
||||||
alert("Please select both Scheme and Year before applying the filter.");
|
alert("Please fill all the fields before applying the filter.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!selectedOption) {
|
|
||||||
alert("Please select either 'Course' or 'Faculty' before applying the filter.");
|
try {
|
||||||
return;
|
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) => {
|
const getSemesters = () => {
|
||||||
setSelectedOption(option);
|
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 (
|
return (
|
||||||
<div className="filter-container">
|
<div className="filter-container">
|
||||||
<div className="filter-form">
|
<div className="filter-form">
|
||||||
|
<select
|
||||||
|
name="department"
|
||||||
|
value={formData.department}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
>
|
||||||
|
<option value="">Select Department</option>
|
||||||
|
<option value="COMPS">COMPS</option>
|
||||||
|
<option value="IT">IT</option>
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
name="program"
|
||||||
|
value={formData.program}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
>
|
||||||
|
<option value="">Select Program</option>
|
||||||
|
<option value="B.Tech">B.Tech</option>
|
||||||
|
<option value="M.Tech">M.Tech</option>
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
name="semester"
|
||||||
|
value={formData.semester}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
>
|
||||||
|
<option value="">Select Semester</option>
|
||||||
|
{getSemesters().map((sem) => (
|
||||||
|
<option key={sem} value={sem}>
|
||||||
|
Semester {sem}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
<select
|
<select
|
||||||
name="scheme"
|
name="scheme"
|
||||||
value={formData.scheme}
|
value={formData.scheme}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
>
|
>
|
||||||
<option value="">Select Scheme</option>
|
<option value="">Select Scheme</option>
|
||||||
<option value="Scheme1">Scheme 1</option>
|
<option value="2020">SVU 2020</option>
|
||||||
<option value="Scheme2">Scheme 2</option>
|
<option value="2023">SVU 2023</option>
|
||||||
</select>
|
</select>
|
||||||
<select
|
|
||||||
name="year"
|
|
||||||
value={formData.year}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
>
|
|
||||||
<option value="">Select Year</option>
|
|
||||||
<option value="1">1st Year</option>
|
|
||||||
<option value="2">2nd Year</option>
|
|
||||||
</select>
|
|
||||||
<button
|
|
||||||
className={selectedOption === "Faculty" ? "active-button" : ""}
|
|
||||||
onClick={() => handleSelectOption("Faculty")}
|
|
||||||
>
|
|
||||||
Faculty
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={selectedOption === "Course" ? "active-button" : ""}
|
|
||||||
onClick={() => handleSelectOption("Course")}
|
|
||||||
>
|
|
||||||
Course
|
|
||||||
</button>
|
|
||||||
<button onClick={handleApplyFilter}>Apply Filter</button>
|
<button onClick={handleApplyFilter}>Apply Filter</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
28
client/src/api.js
Normal file
28
client/src/api.js
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
11
server/models/Appointment.js
Normal file
11
server/models/Appointment.js
Normal file
@@ -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);
|
||||||
13
server/models/Course.js
Normal file
13
server/models/Course.js
Normal file
@@ -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);
|
||||||
11
server/models/Faculty.js
Normal file
11
server/models/Faculty.js
Normal file
@@ -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);
|
||||||
36
server/package-lock.json
generated
36
server/package-lock.json
generated
@@ -25,7 +25,8 @@
|
|||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"passport-local-mongoose": "^8.0.0"
|
"passport-local-mongoose": "^8.0.0",
|
||||||
|
"uuid": "^11.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.0"
|
"nodemon": "^3.1.0"
|
||||||
@@ -712,6 +713,18 @@
|
|||||||
"node": ">=14"
|
"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": {
|
"node_modules/gcp-metadata": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
|
||||||
@@ -898,6 +911,18 @@
|
|||||||
"node": ">=14.0.0"
|
"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": {
|
"node_modules/gopd": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.1.0.tgz",
|
||||||
@@ -2219,16 +2244,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uuid": {
|
"node_modules/uuid": {
|
||||||
"version": "9.0.1",
|
"version": "11.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz",
|
||||||
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
"integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
"https://github.com/sponsors/broofa",
|
"https://github.com/sponsors/broofa",
|
||||||
"https://github.com/sponsors/ctavan"
|
"https://github.com/sponsors/ctavan"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"uuid": "dist/bin/uuid"
|
"uuid": "dist/esm/bin/uuid"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vary": {
|
"node_modules/vary": {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"main": "sever.js",
|
"main": "sever.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"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"
|
"start": "nodemon server.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -34,7 +34,8 @@
|
|||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"passport-local-mongoose": "^8.0.0"
|
"passport-local-mongoose": "^8.0.0",
|
||||||
|
"uuid": "^11.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.0"
|
"nodemon": "^3.1.0"
|
||||||
|
|||||||
51
server/routes/appointmentRoutes.js
Normal file
51
server/routes/appointmentRoutes.js
Normal file
@@ -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;
|
||||||
38
server/routes/courseRoutes.js
Normal file
38
server/routes/courseRoutes.js
Normal file
@@ -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;
|
||||||
29
server/routes/facultyRoutes.js
Normal file
29
server/routes/facultyRoutes.js
Normal file
@@ -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;
|
||||||
@@ -1,25 +1,38 @@
|
|||||||
const express = require("express");
|
const express = require("express");
|
||||||
const User = require("./models/User");
|
const mongoose = require("mongoose");
|
||||||
|
const bodyParser = require("body-parser");
|
||||||
const cors = require("cors");
|
const cors = require("cors");
|
||||||
|
require("dotenv").config();
|
||||||
const passport = require("passport");
|
const passport = require("passport");
|
||||||
const session = require("express-session");
|
const session = require("express-session");
|
||||||
const bodyParser = require("body-parser");
|
|
||||||
const bcrypt = require("bcryptjs");
|
const bcrypt = require("bcryptjs");
|
||||||
const LocalStrategy = require("passport-local").Strategy;
|
const LocalStrategy = require("passport-local").Strategy;
|
||||||
const PasswordRouter = require("./routes/authRoutes");
|
|
||||||
const crypto = require("crypto");
|
const crypto = require("crypto");
|
||||||
const jwt = require("jsonwebtoken");
|
const jwt = require("jsonwebtoken");
|
||||||
const path = require("path");
|
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");
|
const { connectdb } = require("./ConnectionDb");
|
||||||
connectdb();
|
connectdb();
|
||||||
|
|
||||||
|
// MongoDB Connection
|
||||||
|
mongoose
|
||||||
|
.connect(process.env.mongoURI)
|
||||||
|
.then(() => console.log("MongoDB connected"))
|
||||||
|
.catch((err) => console.error("MongoDB connection error:", err));
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
|
app.use(cors());
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(bodyParser.urlencoded({ extended: true }));
|
app.use(bodyParser.urlencoded({ extended: true }));
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
session({
|
session({
|
||||||
secret: "secret",
|
secret: "secret",
|
||||||
@@ -39,7 +52,7 @@ app.use(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Passport configuration
|
// Passport Configuration
|
||||||
require("./config/passport");
|
require("./config/passport");
|
||||||
|
|
||||||
passport.use(
|
passport.use(
|
||||||
@@ -76,7 +89,11 @@ passport.deserializeUser((id, done) => {
|
|||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
app.use("/password", PasswordRouter);
|
app.use("/password", PasswordRouter);
|
||||||
|
app.use("/api/courses", courseRoutes);
|
||||||
|
app.use("/api/faculty", facultyRoutes);
|
||||||
|
app.use("/api/appointments", appointmentRoutes);
|
||||||
|
|
||||||
|
// OAuth Routes
|
||||||
app.get(
|
app.get(
|
||||||
"/auth/google",
|
"/auth/google",
|
||||||
passport.authenticate("google", { scope: ["profile", "email"] })
|
passport.authenticate("google", { scope: ["profile", "email"] })
|
||||||
@@ -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")));
|
app.use(express.static(path.join(__dirname, "../client/build")));
|
||||||
|
|
||||||
// Catch-all route to serve React app
|
// Catch-All Route
|
||||||
app.get("*", (req, res) =>
|
app.get("*", (req, res) =>
|
||||||
res.sendFile(path.join(__dirname, "../client/build/index.html"))
|
res.sendFile(path.join(__dirname, "../client/build/index.html"))
|
||||||
);
|
);
|
||||||
|
|
||||||
const Port = 8080;
|
|
||||||
|
// Start Server
|
||||||
|
const Port = process.env.PORT || 8080;
|
||||||
app.listen(Port, () => {
|
app.listen(Port, () => {
|
||||||
console.log(`Server is Running at port ${Port}`);
|
console.log(`Server is Running at port ${Port}`);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user