Navbar, bulk email (fix toggle error), department consolidated
This commit is contained in:
BIN
client/public/logo_.png
Normal file
BIN
client/public/logo_.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 279 KiB |
@@ -14,6 +14,7 @@ import ConsolidatedTable from "./Pages/ConsolidatedTable";
|
||||
import CourseConsolidated from "./Pages/courseConsolidated";
|
||||
import PrivateRoute from "./components/PrivateRoute";
|
||||
import TokenRefresher from "./components/TokenRefresher";
|
||||
import DepartmentConsolidated from "./Pages/DepartmentConsolidated";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -30,6 +31,7 @@ function App() {
|
||||
<Route path="/courses" element={<PrivateRoute element={<CourseTable />} />} />
|
||||
<Route path="/consolidated" element={<PrivateRoute element={<ConsolidatedTable />} />} />
|
||||
<Route path="/courseConsolidated" element={<PrivateRoute element={<CourseConsolidated />} />} />
|
||||
<Route path="/departmentConsolidated" element={<PrivateRoute element={<DepartmentConsolidated />} />} />
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
|
||||
287
client/src/Pages/DepartmentConsolidated.jsx
Normal file
287
client/src/Pages/DepartmentConsolidated.jsx
Normal file
@@ -0,0 +1,287 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import Navbar from "./Navbar";
|
||||
|
||||
const DepartmentConsolidated = () => {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [data, setData] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const tablesPerPage = 5;
|
||||
const [expandedDepartment, setExpandedDepartment] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
"http://localhost:8080/api/table/department-consolidated",
|
||||
{ withCredentials: true }
|
||||
);
|
||||
setData(response.data);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error("Error fetching table data:", error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
// Filter departments based on search query
|
||||
const filteredDepartments = data.filter((department) =>
|
||||
department.department.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
const totalPages = Math.ceil(filteredDepartments.length / tablesPerPage);
|
||||
const indexOfLastTable = currentPage * tablesPerPage;
|
||||
const indexOfFirstTable = indexOfLastTable - tablesPerPage;
|
||||
const currentDepartments = filteredDepartments.slice(
|
||||
indexOfFirstTable,
|
||||
indexOfLastTable
|
||||
);
|
||||
|
||||
const handleNextPage = () => {
|
||||
if (currentPage < totalPages) setCurrentPage((prevPage) => prevPage + 1);
|
||||
};
|
||||
|
||||
const handlePrevPage = () => {
|
||||
if (currentPage > 1) setCurrentPage((prevPage) => prevPage - 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<div>
|
||||
<h1
|
||||
style={{
|
||||
textAlign: "center",
|
||||
background: "#003366",
|
||||
color: "white",
|
||||
padding: "20px 0",
|
||||
fontSize: "24px",
|
||||
}}
|
||||
>
|
||||
Department-wise Course Tables
|
||||
</h1>
|
||||
|
||||
<div style={{ padding: "10px", marginBottom: "30px" }}>
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Search for a department..."
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "10px",
|
||||
borderRadius: "5px",
|
||||
border: "1px solid #ccc",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
maxHeight: "70vh",
|
||||
overflowY: "auto",
|
||||
border: "1px solid #ccc",
|
||||
padding: "10px",
|
||||
borderRadius: "5px",
|
||||
backgroundColor: "#f9f9f9",
|
||||
}}
|
||||
>
|
||||
{currentDepartments.map((department, index) => (
|
||||
<div key={index} style={{ marginBottom: "20px" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
backgroundColor: "#ffffff",
|
||||
color: "black",
|
||||
padding: "10px",
|
||||
borderRadius: "5px",
|
||||
}}
|
||||
onClick={() =>
|
||||
setExpandedDepartment(
|
||||
expandedDepartment === department.department
|
||||
? null
|
||||
: department.department
|
||||
)
|
||||
}
|
||||
>
|
||||
<h2 style={{ margin: 0 }}>{department.department}</h2>
|
||||
</div>
|
||||
|
||||
{expandedDepartment === department.department && (
|
||||
<div>
|
||||
{department.courses.map((course, courseIndex) => (
|
||||
<div key={courseIndex} style={{ marginTop: "20px" }}>
|
||||
<h3 style={{ marginBottom: "10px" }}>
|
||||
{course.courseName} ({course.courseCode})
|
||||
</h3>
|
||||
<table
|
||||
border="1"
|
||||
style={{
|
||||
width: "100%",
|
||||
textAlign: "left",
|
||||
marginBottom: "20px",
|
||||
}}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Semester</th>
|
||||
<th>Course Code</th>
|
||||
<th>Course Name</th>
|
||||
<th>Exam Type</th>
|
||||
<th>Year</th>
|
||||
<th>Oral/Practical</th>
|
||||
<th>Assessment</th>
|
||||
<th>Reassessment</th>
|
||||
<th>Paper Setting</th>
|
||||
<th>Moderation</th>
|
||||
<th>PwD Paper Setting</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{course.semester}</td>
|
||||
<td>{course.courseCode}</td>
|
||||
<td>{course.courseName}</td>
|
||||
<td>{course.examType}</td>
|
||||
<td>{course.year}</td>
|
||||
<td>
|
||||
{course.oralPracticalTeachers.length > 0 ? (
|
||||
<ul style={{ margin: 0, paddingLeft: "20px" }}>
|
||||
{course.oralPracticalTeachers.map(
|
||||
(teacher, idx) => (
|
||||
<li key={idx}>{teacher}</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
) : (
|
||||
"N/A"
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{course.assessmentTeachers.length > 0 ? (
|
||||
<ul style={{ margin: 0, paddingLeft: "20px" }}>
|
||||
{course.assessmentTeachers.map(
|
||||
(teacher, idx) => (
|
||||
<li key={idx}>{teacher}</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
) : (
|
||||
"N/A"
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{course.reassessmentTeachers.length > 0 ? (
|
||||
<ul style={{ margin: 0, paddingLeft: "20px" }}>
|
||||
{course.reassessmentTeachers.map(
|
||||
(teacher, idx) => (
|
||||
<li key={idx}>{teacher}</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
) : (
|
||||
"N/A"
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{course.paperSettingTeachers.length > 0 ? (
|
||||
<ul style={{ margin: 0, paddingLeft: "20px" }}>
|
||||
{course.paperSettingTeachers.map(
|
||||
(teacher, idx) => (
|
||||
<li key={idx}>{teacher}</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
) : (
|
||||
"N/A"
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{course.moderationTeachers.length > 0 ? (
|
||||
<ul style={{ margin: 0, paddingLeft: "20px" }}>
|
||||
{course.moderationTeachers.map(
|
||||
(teacher, idx) => (
|
||||
<li key={idx}>{teacher}</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
) : (
|
||||
"N/A"
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{course.pwdPaperSettingTeachers.length > 0 ? (
|
||||
<ul style={{ margin: 0, paddingLeft: "20px" }}>
|
||||
{course.pwdPaperSettingTeachers.map(
|
||||
(teacher, idx) => (
|
||||
<li key={idx}>{teacher}</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
) : (
|
||||
"N/A"
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Pagination controls */}
|
||||
<div style={{ textAlign: "center", marginTop: "20px" }}>
|
||||
<button
|
||||
onClick={handlePrevPage}
|
||||
disabled={currentPage === 1}
|
||||
style={{
|
||||
padding: "10px 15px",
|
||||
marginRight: "10px",
|
||||
backgroundColor: currentPage === 1 ? "#ccc" : "#007bff",
|
||||
color: "white",
|
||||
borderRadius: "5px",
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<span>
|
||||
Page {currentPage} of {totalPages}
|
||||
</span>
|
||||
<button
|
||||
onClick={handleNextPage}
|
||||
disabled={currentPage === totalPages}
|
||||
style={{
|
||||
padding: "10px 15px",
|
||||
marginLeft: "10px",
|
||||
backgroundColor: currentPage === totalPages ? "#ccc" : "#007bff",
|
||||
color: "white",
|
||||
borderRadius: "5px",
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DepartmentConsolidated;
|
||||
@@ -150,14 +150,14 @@ function TogglerContainer(props) {
|
||||
<div className="toggle-container">
|
||||
<div className="toggle">
|
||||
<div className="toggle-panel toggle-left">
|
||||
<h1>Welcome to MERN Auth App</h1>
|
||||
<h1>Welcome to Appointment to Examiner</h1>
|
||||
<p>Already Have an Account?</p>
|
||||
<button className="hidden" onClick={props.ToggleSign}>
|
||||
Sign In
|
||||
</button>
|
||||
</div>
|
||||
<div className="toggle-panel toggle-right">
|
||||
<h1>Welcome to MERN Auth App</h1>
|
||||
<h1>Welcome to Appointment to Examiner</h1>
|
||||
<p>Don't have an account? Create one</p>
|
||||
<button className="hidden" onClick={props.ToggleSign}>
|
||||
Sign Up
|
||||
|
||||
@@ -61,3 +61,30 @@ width: 100%;
|
||||
.button-container button:hover {
|
||||
background-color: #ffcccc; /* Hover color */
|
||||
}
|
||||
|
||||
.logout-button{
|
||||
background-color: #b22222;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
|
||||
flex: 1; /* Fields will take up equal space */
|
||||
max-width: 200px; /* Ensures fields don't get too wide */
|
||||
padding: 8px 38px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.navbar-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* margin-right: auto; */
|
||||
}
|
||||
|
||||
.logo-img {
|
||||
width: 60px; /* Adjust size as needed */
|
||||
height: auto;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FaUserCircle } from "react-icons/fa";
|
||||
import { NavLink, useNavigate } from "react-router-dom"; // Import NavLink for navigation
|
||||
import "./Navbar.css"; // Navbar-specific styles
|
||||
@@ -43,20 +43,25 @@ const Navbar = () => {
|
||||
<header className="navbar">
|
||||
<div className="navbar-container">
|
||||
<ToastContainer />
|
||||
{/* Appointment To Examiner text at the left */}
|
||||
<NavLink to="/Welcome" className="navbar-title nav-btn">
|
||||
Appointment To Examiner
|
||||
|
||||
<NavLink to="/Welcome" className="navbar-logo">
|
||||
<img src="logo_.png" alt="Logo" className="logo-img" />
|
||||
</NavLink>
|
||||
|
||||
{/* Consolidated buttons in the center */}
|
||||
|
||||
<div className="button-container">
|
||||
|
||||
{/* Consolidated buttons in the center */}
|
||||
|
||||
<NavLink to="/consolidated" className="consolidated-button nav-btn">
|
||||
Faculty
|
||||
Consolidated
|
||||
Faculty Consolidated
|
||||
</NavLink>
|
||||
<NavLink to="/courseConsolidated" className="consolidated-button nav-btn">
|
||||
Course Consolidated
|
||||
</NavLink>
|
||||
<NavLink to="/departmentConsolidated" className="navbar-title nav-btn">
|
||||
Department Consolidated
|
||||
</NavLink>
|
||||
</div>
|
||||
<div>
|
||||
<button className="logout-button" onClick={handleLogout}>
|
||||
|
||||
@@ -145,4 +145,75 @@ router.get("/course-consolidated", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/department-consolidated", async (req, res) => {
|
||||
try {
|
||||
const appointments = await Appointment.find();
|
||||
const courses = await Course.find();
|
||||
|
||||
// Group appointments by department and course
|
||||
const groupedByDepartment = {};
|
||||
|
||||
appointments.forEach((appointment) => {
|
||||
const course = courses.find((c) => c.courseId === appointment.courseId);
|
||||
|
||||
if (!course) return; // Skip if course not found
|
||||
|
||||
const department = course.department;
|
||||
|
||||
if (!groupedByDepartment[department]) {
|
||||
groupedByDepartment[department] = {
|
||||
department: department,
|
||||
courses: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Find or create the course in the department
|
||||
let courseData = groupedByDepartment[department].courses.find(
|
||||
(c) => c.courseCode === appointment.courseId
|
||||
);
|
||||
|
||||
if (!courseData) {
|
||||
courseData = {
|
||||
courseCode: appointment.courseId,
|
||||
courseName: appointment.courseName,
|
||||
semester: course.semester,
|
||||
examType: course.scheme,
|
||||
year: course.program,
|
||||
oralPracticalTeachers: [],
|
||||
assessmentTeachers: [],
|
||||
reassessmentTeachers: [],
|
||||
paperSettingTeachers: [],
|
||||
moderationTeachers: [],
|
||||
pwdPaperSettingTeachers: [],
|
||||
};
|
||||
groupedByDepartment[department].courses.push(courseData);
|
||||
}
|
||||
|
||||
// Add the faculty name to the appropriate task
|
||||
if (appointment.task === "oralsPracticals") {
|
||||
courseData.oralPracticalTeachers.push(appointment.facultyName);
|
||||
} else if (appointment.task === "assessment") {
|
||||
courseData.assessmentTeachers.push(appointment.facultyName);
|
||||
} else if (appointment.task === "reassessment") {
|
||||
courseData.reassessmentTeachers.push(appointment.facultyName);
|
||||
} else if (appointment.task === "paperSetting") {
|
||||
courseData.paperSettingTeachers.push(appointment.facultyName);
|
||||
} else if (appointment.task === "moderation") {
|
||||
courseData.moderationTeachers.push(appointment.facultyName);
|
||||
} else if (appointment.task === "pwdPaperSetter") {
|
||||
courseData.pwdPaperSettingTeachers.push(appointment.facultyName);
|
||||
}
|
||||
});
|
||||
|
||||
// Convert to array format
|
||||
const consolidatedData = Object.values(groupedByDepartment);
|
||||
|
||||
res.status(200).json(consolidatedData);
|
||||
} catch (error) {
|
||||
console.error("Error fetching department consolidated data:", error);
|
||||
res.status(500).json({ message: "Failed to fetch department consolidated data" });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user