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 CourseConsolidated from "./Pages/courseConsolidated";
|
||||||
import PrivateRoute from "./components/PrivateRoute";
|
import PrivateRoute from "./components/PrivateRoute";
|
||||||
import TokenRefresher from "./components/TokenRefresher";
|
import TokenRefresher from "./components/TokenRefresher";
|
||||||
|
import DepartmentConsolidated from "./Pages/DepartmentConsolidated";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -30,6 +31,7 @@ function App() {
|
|||||||
<Route path="/courses" element={<PrivateRoute element={<CourseTable />} />} />
|
<Route path="/courses" element={<PrivateRoute element={<CourseTable />} />} />
|
||||||
<Route path="/consolidated" element={<PrivateRoute element={<ConsolidatedTable />} />} />
|
<Route path="/consolidated" element={<PrivateRoute element={<ConsolidatedTable />} />} />
|
||||||
<Route path="/courseConsolidated" element={<PrivateRoute element={<CourseConsolidated />} />} />
|
<Route path="/courseConsolidated" element={<PrivateRoute element={<CourseConsolidated />} />} />
|
||||||
|
<Route path="/departmentConsolidated" element={<PrivateRoute element={<DepartmentConsolidated />} />} />
|
||||||
</Routes>
|
</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-container">
|
||||||
<div className="toggle">
|
<div className="toggle">
|
||||||
<div className="toggle-panel toggle-left">
|
<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>
|
<p>Already Have an Account?</p>
|
||||||
<button className="hidden" onClick={props.ToggleSign}>
|
<button className="hidden" onClick={props.ToggleSign}>
|
||||||
Sign In
|
Sign In
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="toggle-panel toggle-right">
|
<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>
|
<p>Don't have an account? Create one</p>
|
||||||
<button className="hidden" onClick={props.ToggleSign}>
|
<button className="hidden" onClick={props.ToggleSign}>
|
||||||
Sign Up
|
Sign Up
|
||||||
|
|||||||
@@ -61,3 +61,30 @@ width: 100%;
|
|||||||
.button-container button:hover {
|
.button-container button:hover {
|
||||||
background-color: #ffcccc; /* Hover color */
|
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 { FaUserCircle } from "react-icons/fa";
|
||||||
import { NavLink, useNavigate } from "react-router-dom"; // Import NavLink for navigation
|
import { NavLink, useNavigate } from "react-router-dom"; // Import NavLink for navigation
|
||||||
import "./Navbar.css"; // Navbar-specific styles
|
import "./Navbar.css"; // Navbar-specific styles
|
||||||
@@ -43,19 +43,24 @@ const Navbar = () => {
|
|||||||
<header className="navbar">
|
<header className="navbar">
|
||||||
<div className="navbar-container">
|
<div className="navbar-container">
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
{/* Appointment To Examiner text at the left */}
|
|
||||||
<NavLink to="/Welcome" className="navbar-title nav-btn">
|
<NavLink to="/Welcome" className="navbar-logo">
|
||||||
Appointment To Examiner
|
<img src="logo_.png" alt="Logo" className="logo-img" />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
{/* Consolidated buttons in the center */}
|
|
||||||
<div className="button-container">
|
<div className="button-container">
|
||||||
|
|
||||||
|
{/* Consolidated buttons in the center */}
|
||||||
|
|
||||||
<NavLink to="/consolidated" className="consolidated-button nav-btn">
|
<NavLink to="/consolidated" className="consolidated-button nav-btn">
|
||||||
Faculty
|
Faculty Consolidated
|
||||||
Consolidated
|
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink to="/courseConsolidated" className="consolidated-button nav-btn">
|
<NavLink to="/courseConsolidated" className="consolidated-button nav-btn">
|
||||||
Course Consolidated
|
Course Consolidated
|
||||||
|
</NavLink>
|
||||||
|
<NavLink to="/departmentConsolidated" className="navbar-title nav-btn">
|
||||||
|
Department Consolidated
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -66,16 +71,16 @@ const Navbar = () => {
|
|||||||
|
|
||||||
{/* User icon at the right */}
|
{/* User icon at the right */}
|
||||||
<NavLink to="/accounts" className="user-icon-link">
|
<NavLink to="/accounts" className="user-icon-link">
|
||||||
{user && user.profilePicture ? (
|
{user && user.profilePicture ? (
|
||||||
<img
|
<img
|
||||||
src={user.profilePicture}
|
src={user.profilePicture}
|
||||||
alt="Profile"
|
alt="Profile"
|
||||||
className="user-icon"
|
className="user-icon"
|
||||||
style={{ width: '40px', height: '40px', borderRadius: '50%' }}
|
style={{ width: '40px', height: '40px', borderRadius: '50%' }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FaUserCircle className="user-icon" />
|
<FaUserCircle className="user-icon" />
|
||||||
)}
|
)}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -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;
|
module.exports = router;
|
||||||
|
|||||||
Reference in New Issue
Block a user