From f95ad08cd1a984c396b4439e2a0e99222869ff4a Mon Sep 17 00:00:00 2001 From: Sanjana Reddy Date: Fri, 5 Sep 2025 21:29:44 +0530 Subject: [PATCH 1/6] quiz dates --- client/src/api/Quiz.js | 66 +++++++++++++++++++ .../screens/browse/components/quizcard.jsx | 19 ++++++ .../screens/browse/components/quizcard.scss | 29 ++++++++ client/src/screens/browse/index.jsx | 43 +++++------- .../dashboard/components/examcard/index.jsx | 22 +++---- .../screens/dashboard/components/quizcard.jsx | 15 +++++ .../dashboard/components/quizcard.scss | 49 ++++++++++++++ client/src/screens/dashboard/index.jsx | 63 +++++++++++++++--- server/controllers/quiz.controller.js | 0 server/index.js | 2 + server/models/quiz.model.js | 0 server/modules/quiz.js | 0 server/modules/quizzes/quizzes.controller.js | 44 +++++++++++++ server/modules/quizzes/quizzes.model.js | 25 +++++++ server/modules/quizzes/quizzes.routes.js | 13 ++++ 15 files changed, 345 insertions(+), 45 deletions(-) create mode 100644 client/src/api/Quiz.js create mode 100644 client/src/screens/browse/components/quizcard.jsx create mode 100644 client/src/screens/browse/components/quizcard.scss create mode 100644 client/src/screens/dashboard/components/quizcard.jsx create mode 100644 client/src/screens/dashboard/components/quizcard.scss create mode 100644 server/controllers/quiz.controller.js create mode 100644 server/models/quiz.model.js create mode 100644 server/modules/quiz.js create mode 100644 server/modules/quizzes/quizzes.controller.js create mode 100644 server/modules/quizzes/quizzes.model.js create mode 100644 server/modules/quizzes/quizzes.routes.js diff --git a/client/src/api/Quiz.js b/client/src/api/Quiz.js new file mode 100644 index 00000000..8c724f9e --- /dev/null +++ b/client/src/api/Quiz.js @@ -0,0 +1,66 @@ +import { toast } from "react-toastify"; +import server from "./server"; +import { getUser } from "./User"; +import { getCourse } from "./Course"; + +export const createQuizEvent = async (eventName, eventDate, course) => { + try { + // const courseName = await getCourse(course); + // if (!courseName) throw new Error("Course not found"); + + const response = await fetch(`${server}/api/quiz/create`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + credentials: "include", + body: JSON.stringify({ + eventName, + eventDate, + course, + }), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || "Failed to create quiz event"); + } + + return data; + } catch (error) { + throw error; + } +}; + +export const getQuizEvents = async () => { + try { + const { data: user } = await getUser(); + if (!user) { + throw new Error("User not found"); + } + console.log("User data:", user.user); + const response = await fetch(`${server}/api/quiz/events`, { + method: "GET", + credentials: "include", + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || "Failed to fetch quiz events"); + } + console.log("All quiz events data:", data.data); + if (data.data) { + const userEvents = data.data.filter((event) => + user?.courses.some((course) => course.code === event.course) + ); + console.log("User-specific quiz events:", userEvents); + return userEvents; + } else { + throw new Error(data.message || "Failed to fetch quiz events data"); + } + } catch (error) { + throw error; + } +}; diff --git a/client/src/screens/browse/components/quizcard.jsx b/client/src/screens/browse/components/quizcard.jsx new file mode 100644 index 00000000..b4fbbde0 --- /dev/null +++ b/client/src/screens/browse/components/quizcard.jsx @@ -0,0 +1,19 @@ +import React from "react"; +import "./quizcard.scss"; + +const QuizCard = ({ code, name, date, day, color }) => { + return ( +
+
+ {code} + {name} +
+
+ {date} + {day} +
+
+ ); +}; + +export default QuizCard; diff --git a/client/src/screens/browse/components/quizcard.scss b/client/src/screens/browse/components/quizcard.scss new file mode 100644 index 00000000..c9003530 --- /dev/null +++ b/client/src/screens/browse/components/quizcard.scss @@ -0,0 +1,29 @@ +.quizcard { + border-radius: 12px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + padding: 16px; + margin-bottom: 16px; + display: flex; + flex-direction: column; + background: #f5f5f5; + transition: box-shadow 0.2s; +} +.quizcard-header { + display: flex; + justify-content: space-between; + font-weight: bold; + font-size: 1.1em; +} +.quizcard-code { + color: #6c63ff; +} +.quizcard-name { + color: #333; +} +.quizcard-date { + margin-top: 8px; + font-size: 0.95em; + color: #555; + display: flex; + justify-content: space-between; +} diff --git a/client/src/screens/browse/index.jsx b/client/src/screens/browse/index.jsx index f8843457..6f4e111a 100644 --- a/client/src/screens/browse/index.jsx +++ b/client/src/screens/browse/index.jsx @@ -483,28 +483,23 @@ const BrowseScreen = () => { ) : (

MY COURSES

- {user.user?.courses?.map((course, idx) => { - return ( - - ); - })} - {user.localCourses?.map((course, idx) => { - return ( - - ); - })} + {user.user?.courses?.map((course, idx) => ( + + ))} + {user.localCourses?.map((course, idx) => ( + + ))} {user.user?.readOnly?.length > 0 &&

OTHERS

} - {user.user?.readOnly?.map((course, idx) => ( { )} {user.user?.isBR && user.user?.previousCourses?.length > 0 && - user.user?.previousCourses?.map((course, idx) => { - return ( - - ); - })} + user.user?.previousCourses?.map((course, idx) => ( + + ))}
)} {!isMobile && ( diff --git a/client/src/screens/dashboard/components/examcard/index.jsx b/client/src/screens/dashboard/components/examcard/index.jsx index 9b8871bf..f665e035 100644 --- a/client/src/screens/dashboard/components/examcard/index.jsx +++ b/client/src/screens/dashboard/components/examcard/index.jsx @@ -1,16 +1,16 @@ import "./styles.scss"; const ExamCard = ({ days, name, color }) => { - return ( -
-
-

{days ? days : 0}

-
-
-

Days for

-

{name ? name : "Mid-Sem Exam"}

-
-
- ); + return ( +
+
+

{days ? days : 0}

+
+
+

Days for

+

{name ? name : "Mid-Sem Exam"}

+
+
+ ); }; export default ExamCard; diff --git a/client/src/screens/dashboard/components/quizcard.jsx b/client/src/screens/dashboard/components/quizcard.jsx new file mode 100644 index 00000000..af65884e --- /dev/null +++ b/client/src/screens/dashboard/components/quizcard.jsx @@ -0,0 +1,15 @@ +import React from "react"; +import "./quizcard.scss"; + +const QuizCard = ({ code, name, date, day, color }) => { + return ( +
+
{code}
+
{name}
+
{date}
+
{day}
+
+ ); +}; + +export default QuizCard; diff --git a/client/src/screens/dashboard/components/quizcard.scss b/client/src/screens/dashboard/components/quizcard.scss new file mode 100644 index 00000000..a292b2d1 --- /dev/null +++ b/client/src/screens/dashboard/components/quizcard.scss @@ -0,0 +1,49 @@ +.quizcard { + width: 190px; + min-height: 150px; + background-color: #fff; + padding: 10px; + margin: 10px; + transition: 150ms ease; + position: relative; + flex-shrink: 0; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: flex-start; +} +.quizcard-code { + background-color: #000; + color: #fff; + padding: 3px 10px; + //text-align: center; + text-align: left; + font-family: "Bold"; + font-size: 0.9rem; + margin-bottom: 10px; + margin-top: 0; + width: fit-content; +} +.quizcard-name { + color: #333; + font-family: "Bold"; + font-size: 1.4rem; + text-align: left; + margin-bottom: 10px; + width: 100%; +} +.quizcard-date { + color: #555; + font-family: "Bold"; + font-size: 1.1rem; + text-align: left; + margin-bottom: 10px; + width: 100%; +} +.quizcard-day { + color: #555; + font-family: "Bold"; + font-size: 0.95em; + text-align: left; + width: 100%; +} diff --git a/client/src/screens/dashboard/index.jsx b/client/src/screens/dashboard/index.jsx index b9b8f431..f29272e3 100644 --- a/client/src/screens/dashboard/index.jsx +++ b/client/src/screens/dashboard/index.jsx @@ -7,7 +7,11 @@ import NavBar from "../../components/navbar"; import SubHeading from "../../components/subheading"; import CourseCard from "./components/coursecard"; import ContributionBanner from "./components/contributionbanner"; +import QuizCard from "./components/quizcard"; +import { getQuizEvents } from "../../api/Quiz"; +//import { getQuizEventByUserId, getQuizEvents } from "../../api/Quiz"; import Footer from "../../components/footer"; + import FavouriteCard from "./components/favouritecard"; import { ChangeCurrentCourse, ResetFileBrowserState } from "../../actions/filebrowser_actions"; @@ -26,6 +30,19 @@ import { AddNewCourseAPI, GetExamDates } from "../../api/User"; import { toast } from "react-toastify"; const Dashboard = () => { + const [quizzes, setQuizzes] = useState([]); + useEffect(() => { + async function fetchQuizzes() { + try { + const data = await getQuizEvents(); + console.log("Quizzes fetched:", data); + setQuizzes(data || []); + } catch (err) { + setQuizzes([]); + } + } + fetchQuizzes(); + }, []); const dispatch = useDispatch(); const navigate = useNavigate(); const user = useSelector((state) => state.user); @@ -185,6 +202,43 @@ const Dashboard = () => { }} /> */} + + {/* MY QUIZZES */} + + +
+ {quizzes.length > 0 ? ( + [...quizzes] + .sort((a, b) => new Date(a.eventDate) - new Date(b.eventDate)) + .map((quiz, idx) => ( + + )) + ) : ( +
+ No quizzes scheduled +
+ )} +
@@ -212,15 +266,6 @@ const Dashboard = () => { { - // dispatch( - // AddNewCourseLocal({ - // _id: "638f1709897b3c84b7d8d32c", - // name: "Introduction to Engineering Drawing", - // code: "ce101", - // color: "#DBCEFF", - // }) - // ); - // console.log(user); addCourseModalShowHandler(); }} /> diff --git a/server/controllers/quiz.controller.js b/server/controllers/quiz.controller.js new file mode 100644 index 00000000..e69de29b diff --git a/server/index.js b/server/index.js index e0d8c9eb..6e498e6e 100644 --- a/server/index.js +++ b/server/index.js @@ -32,6 +32,7 @@ import fileRoutes from "./modules/file/file.routes.js"; import folderRoutes from "./modules/folder/folder.routes.js"; import yearRoutes from "./modules/year/year.routes.js"; import links from "./links.js"; +import quizRoutes from "./modules/quizzes/quizzes.routes.js"; const app = express(); @@ -75,6 +76,7 @@ app.use("/api/br", brRoutes); app.use("/api/files", fileRoutes); app.use("/api/folder", folderRoutes); app.use("/api/year", yearRoutes); +app.use("/api/quiz", quizRoutes); app.use( "/homepage", diff --git a/server/models/quiz.model.js b/server/models/quiz.model.js new file mode 100644 index 00000000..e69de29b diff --git a/server/modules/quiz.js b/server/modules/quiz.js new file mode 100644 index 00000000..e69de29b diff --git a/server/modules/quizzes/quizzes.controller.js b/server/modules/quizzes/quizzes.controller.js new file mode 100644 index 00000000..d03dab3d --- /dev/null +++ b/server/modules/quizzes/quizzes.controller.js @@ -0,0 +1,44 @@ +import quizEventModel from "./quizzes.model.js"; + +export const createQuizEvent = async (req, res) => { + try { + const {eventName, eventDate, course} = req.body; + if(!eventName || !eventDate || !course){ + console.log("All fields are required"); + return res.status(400).json({message: "All fields are required", success: false}); + } + + const newQuizEvent = new quizEventModel({ + eventName, + eventDate, + course, + }); + + const savedEvent = await newQuizEvent.save(); + res.status(201).json({ + success: true, + message: "Event Created Successfully", + data: savedEvent, + }); + } catch (error){ + console.log(error); + res.status(500).json({ + success: false, + message: "Error creating quiz event", + error: error.message, + }); + } +} +export const getQuizEvents = async (req,res) =>{ + try { + const quizEvents = await quizEventModel.find().sort({eventDate: -1}); + + res.status(200).json({message: "Quiz events retrieved successfully", success: true, data: quizEvents}); + } catch (error) { + res.status(500).json({ + success: false, + message: "Error retrieving quiz events", + error: error.message, + }); + } +} diff --git a/server/modules/quizzes/quizzes.model.js b/server/modules/quizzes/quizzes.model.js new file mode 100644 index 00000000..f70a659b --- /dev/null +++ b/server/modules/quizzes/quizzes.model.js @@ -0,0 +1,25 @@ +import mongoose from "mongoose"; + +const quizEventSchema = new mongoose.Schema({ + eventName: { + type: String, + required: true, + trim: true + }, + eventDate: { + type: Date, + required: true + }, + course: { + type: String, + required: true, + trim: true + }, + createdAt: { + type: Date, + default: Date.now + } +}); + +const QuizEvent = mongoose.model('QuizEvent', quizEventSchema); +export default QuizEvent; diff --git a/server/modules/quizzes/quizzes.routes.js b/server/modules/quizzes/quizzes.routes.js new file mode 100644 index 00000000..cf3a89b3 --- /dev/null +++ b/server/modules/quizzes/quizzes.routes.js @@ -0,0 +1,13 @@ +import express from "express"; +import { createQuizEvent, getQuizEvents } from "./quizzes.controller.js"; +import {isBR } from "../../middleware/isBR.js"; + +const router = express.Router(); + +// Create a new quiz event (BRs only) +router.post('/create', createQuizEvent); + +// Get all quiz events +router.get('/events', getQuizEvents); + +export default router; From c2932b6d8f356ea8a17f65c8802d28fff091d5ea Mon Sep 17 00:00:00 2001 From: Sanjana Reddy Date: Fri, 5 Sep 2025 21:39:15 +0530 Subject: [PATCH 2/6] Remove unwanted quiz-related files --- .../screens/browse/components/quizcard.jsx | 19 ------------ .../screens/browse/components/quizcard.scss | 29 ------------------- server/controllers/quiz.controller.js | 0 server/models/quiz.model.js | 0 server/modules/quiz.js | 0 5 files changed, 48 deletions(-) delete mode 100644 client/src/screens/browse/components/quizcard.jsx delete mode 100644 client/src/screens/browse/components/quizcard.scss delete mode 100644 server/controllers/quiz.controller.js delete mode 100644 server/models/quiz.model.js delete mode 100644 server/modules/quiz.js diff --git a/client/src/screens/browse/components/quizcard.jsx b/client/src/screens/browse/components/quizcard.jsx deleted file mode 100644 index b4fbbde0..00000000 --- a/client/src/screens/browse/components/quizcard.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react"; -import "./quizcard.scss"; - -const QuizCard = ({ code, name, date, day, color }) => { - return ( -
-
- {code} - {name} -
-
- {date} - {day} -
-
- ); -}; - -export default QuizCard; diff --git a/client/src/screens/browse/components/quizcard.scss b/client/src/screens/browse/components/quizcard.scss deleted file mode 100644 index c9003530..00000000 --- a/client/src/screens/browse/components/quizcard.scss +++ /dev/null @@ -1,29 +0,0 @@ -.quizcard { - border-radius: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); - padding: 16px; - margin-bottom: 16px; - display: flex; - flex-direction: column; - background: #f5f5f5; - transition: box-shadow 0.2s; -} -.quizcard-header { - display: flex; - justify-content: space-between; - font-weight: bold; - font-size: 1.1em; -} -.quizcard-code { - color: #6c63ff; -} -.quizcard-name { - color: #333; -} -.quizcard-date { - margin-top: 8px; - font-size: 0.95em; - color: #555; - display: flex; - justify-content: space-between; -} diff --git a/server/controllers/quiz.controller.js b/server/controllers/quiz.controller.js deleted file mode 100644 index e69de29b..00000000 diff --git a/server/models/quiz.model.js b/server/models/quiz.model.js deleted file mode 100644 index e69de29b..00000000 diff --git a/server/modules/quiz.js b/server/modules/quiz.js deleted file mode 100644 index e69de29b..00000000 From fc0b6d7896f54d080b80ecf258d84500dbcb06b5 Mon Sep 17 00:00:00 2001 From: Atish <125atishprak@gmail.com> Date: Fri, 5 Sep 2025 22:08:52 +0530 Subject: [PATCH 3/6] backend quiz events --- client/src/api/Quiz.js | 140 ++++++++++++++ .../browse/components/folder-info/index.jsx | 20 ++ .../components/quiz-schedule-modal/index.jsx | 107 +++++++++++ .../quiz-schedule-modal/styles.scss | 175 ++++++++++++++++++ client/src/screens/dashboard/index.jsx | 3 +- server/index.js | 2 + server/modules/quizzes/quizzes.controller.js | 70 +++++++ server/modules/quizzes/quizzes.model.js | 30 +++ server/modules/quizzes/quizzes.routes.js | 15 ++ 9 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 client/src/api/Quiz.js create mode 100644 client/src/screens/browse/components/quiz-schedule-modal/index.jsx create mode 100644 client/src/screens/browse/components/quiz-schedule-modal/styles.scss create mode 100644 server/modules/quizzes/quizzes.controller.js create mode 100644 server/modules/quizzes/quizzes.model.js create mode 100644 server/modules/quizzes/quizzes.routes.js diff --git a/client/src/api/Quiz.js b/client/src/api/Quiz.js new file mode 100644 index 00000000..12c9ac34 --- /dev/null +++ b/client/src/api/Quiz.js @@ -0,0 +1,140 @@ +import { toast } from "react-toastify"; +import server from "./server"; +import { getUser } from "./User"; +import { getCourse } from "./Course"; + +export const createQuizEvent = async (eventName, eventDate, course) => { + try { + const response = await fetch(`${server}/api/quiz/create`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + eventName, + eventDate, + course, + }) + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Failed to create quiz event'); + } + + return data; + } catch (error) { + throw error; + } +}; +export const getAllQuizEvents = async () => { + try { + const response = await fetch(`${server}/api/quiz/events`, { + method: 'GET', + credentials: 'include' + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Failed to fetch quiz events'); + } + const events = data.data.filter(event => { + const currDate = new Date(); + const eventDate = new Date(event.eventDate); + if (eventDate.getTime() >= currDate.getTime()){ + return true; + } + else{ + deleteQuizEvent(event._id); + return false; + } + }) + return events || []; + } catch (error) { + throw error; + } +}; + +export const getQuizEvents = async () => { + try { + console.log("Getting Quizzes") + const { data: user } = await getUser(); + if(!user) { + throw new Error('User not found'); + } + const response = await fetch(`${server}/api/quiz/events`, { + method: 'GET', + credentials: 'include' + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Failed to fetch quiz events'); + } + if(data.data) { + // const userEvents = data.data.filter(event => + // user?.courses?.some(course => course.code === event.course) + // ); + const userEvents = data.data.filter(event =>{ + if (user?.courses?.some(course => course.code === event.course)){ + const currDate = new Date(); + const eventDate = new Date(event.eventDate); + if(eventDate.getTime() >= currDate.getTime()){ + return true; + } + else{ + deleteQuizEvent(event._id); + return false; + } + } + }) + return userEvents; + } else { + throw new Error(data.message || 'Failed to fetch quiz events data'); + } + } catch (error) { + throw error; + } +}; + +export const modifyQuizEvent = async (eventId) => { + try { + const response = await fetch(`${server}/api/quiz/modify/${eventId}`, { + method: 'PUT', + credentials: 'include' + }); + const data = await response.json(); + if(!response.ok) { + toast.error(data.message || 'Failed to modify quiz event'); + return null; + } + return data; + } + catch (error) { + toast.error(error.message || 'Failed to modify quiz event'); + return null; + } +} +export const deleteQuizEvent = async (eventId) => { + try { + console.log("deleting Quizzes") + const response = await fetch(`${server}/api/quiz/delete/${eventId}`, { + method: 'DELETE', + credentials: 'include' + }); + const data = await response.json(); + if(!response.ok) { + toast.error(data.message || 'Failed to delete quiz event'); + return null; + } + return data; + } + catch (error) { + toast.error(error.message || 'Failed to delete quiz event'); + return null; + } +} diff --git a/client/src/screens/browse/components/folder-info/index.jsx b/client/src/screens/browse/components/folder-info/index.jsx index f4bb11eb..03fc9a9e 100644 --- a/client/src/screens/browse/components/folder-info/index.jsx +++ b/client/src/screens/browse/components/folder-info/index.jsx @@ -20,6 +20,7 @@ import server from "../../../../api/server"; import JSZip from "jszip"; import { saveAs } from "file-saver"; import { fetchFolder } from "../../../../api/Folder"; +import QuizScheduleModal from "../quiz-schedule-modal"; const FolderInfo = ({ isBR, @@ -39,6 +40,7 @@ const FolderInfo = ({ const [childType, setChildType] = useState("File"); const [isAdding, setIsAdding] = useState(false); const [isDownloading, setIsDownloading] = useState(false); + const [showQuizModal, setShowQuizModal] = useState(false); const user = useSelector((state) => state.user.user); const isReadOnlyCourse = user?.readOnly?.some( @@ -297,6 +299,17 @@ const FolderInfo = ({ )} + + {/* Schedule Quiz button - only for BRs */} + {!isReadOnlyCourse && isBR && ( + + )} )} @@ -314,6 +327,13 @@ const FolderInfo = ({ onCancel={() => setShowConfirm(false)} /> )} + + {/* Quiz Schedule Modal */} + setShowQuizModal(false)} + courseCode={courseCode} + /> ); }; diff --git a/client/src/screens/browse/components/quiz-schedule-modal/index.jsx b/client/src/screens/browse/components/quiz-schedule-modal/index.jsx new file mode 100644 index 00000000..9011df04 --- /dev/null +++ b/client/src/screens/browse/components/quiz-schedule-modal/index.jsx @@ -0,0 +1,107 @@ +import React, { useState } from "react"; +import { createQuizEvent } from "../../../../api/Quiz"; +import { toast } from "react-toastify"; +import "./styles.scss"; + +const QuizScheduleModal = ({ isOpen, onClose, courseCode }) => { + const [eventName, setEventName] = useState(""); + const [eventDate, setEventDate] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleSubmit = async (e) => { + e.preventDefault(); + + if (!eventName.trim() || !eventDate) { + toast.error("Please fill in all fields"); + return; + } + + setIsSubmitting(true); + + try { + await createQuizEvent(eventName, eventDate, courseCode); + toast.success("Quiz event scheduled successfully!"); + setEventName(""); + setEventDate(""); + onClose(); + } catch (error) { + toast.error(error.message || "Failed to schedule quiz event"); + } finally { + setIsSubmitting(false); + } + }; + + const handleClose = () => { + if (!isSubmitting) { + setEventName(""); + setEventDate(""); + onClose(); + } + }; + + if (!isOpen) return null; + + return ( +
+
e.stopPropagation()}> +
+

Schedule Quiz

+ +
+ +
+
+ + setEventName(e.target.value)} + placeholder="Enter quiz name" + disabled={isSubmitting} + required + /> +
+ +
+ + setEventDate(e.target.value)} + disabled={isSubmitting} + required + /> +
+ +
+ + +
+
+
+
+ ); +}; + +export default QuizScheduleModal; diff --git a/client/src/screens/browse/components/quiz-schedule-modal/styles.scss b/client/src/screens/browse/components/quiz-schedule-modal/styles.scss new file mode 100644 index 00000000..f66c0861 --- /dev/null +++ b/client/src/screens/browse/components/quiz-schedule-modal/styles.scss @@ -0,0 +1,175 @@ +.quiz-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + backdrop-filter: blur(2px); +} + +.quiz-modal { + background: white; + border-radius: 12px; + padding: 24px; + width: 90%; + max-width: 500px; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + animation: modalSlideIn 0.3s ease-out; +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(-20px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.quiz-modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 24px; + padding-bottom: 16px; + border-bottom: 1px solid #e5e7eb; + + h3 { + margin: 0; + font-size: 1.5rem; + font-weight: 600; + color: #111827; + } + + .close-button { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: #6b7280; + padding: 4px; + border-radius: 4px; + transition: all 0.2s; + + &:hover { + background-color: #f3f4f6; + color: #374151; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + } +} + +.quiz-modal-form { + .form-group { + margin-bottom: 20px; + + label { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: #374151; + font-size: 0.875rem; + } + + input { + width: 100%; + padding: 12px 16px; + border: 1px solid #d1d5db; + border-radius: 8px; + font-size: 1rem; + transition: border-color 0.2s, box-shadow 0.2s; + + &:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); + } + + &:disabled { + background-color: #f9fafb; + cursor: not-allowed; + } + + &[type="datetime-local"] { + font-family: inherit; + } + } + } + + .form-actions { + display: flex; + gap: 12px; + justify-content: flex-end; + margin-top: 24px; + padding-top: 20px; + border-top: 1px solid #e5e7eb; + + .btn { + padding: 12px 24px; + border-radius: 8px; + font-weight: 500; + font-size: 0.875rem; + cursor: pointer; + transition: all 0.2s; + border: none; + min-width: 100px; + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + } + + .btn-secondary { + background-color: #f3f4f6; + color: #374151; + + &:hover:not(:disabled) { + background-color: #e5e7eb; + } + } + + .btn-primary { + background-color: #3b82f6; + color: white; + + &:hover:not(:disabled) { + background-color: #2563eb; + } + } + } +} + +// Mobile responsiveness +@media (max-width: 640px) { + .quiz-modal { + margin: 16px; + padding: 20px; + width: calc(100% - 32px); + } + + .quiz-modal-header h3 { + font-size: 1.25rem; + } + + .form-actions { + flex-direction: column; + + .btn { + width: 100%; + } + } +} diff --git a/client/src/screens/dashboard/index.jsx b/client/src/screens/dashboard/index.jsx index b9b8f431..0c4a4387 100644 --- a/client/src/screens/dashboard/index.jsx +++ b/client/src/screens/dashboard/index.jsx @@ -24,6 +24,7 @@ import { AddNewCourseLocal, ClearLocalCourses } from "../../actions/user_actions import AddCourseModal from "./components/addcoursemodal"; import { AddNewCourseAPI, GetExamDates } from "../../api/User"; import { toast } from "react-toastify"; +import { getQuizEvents } from "../../api/Quiz"; const Dashboard = () => { const dispatch = useDispatch(); @@ -32,7 +33,7 @@ const Dashboard = () => { const [midSem, setMidSem] = useState(0); const [endSem, setEndSem] = useState(0); - + getQuizEvents(); const contributionHandler = (event) => { const collection = document.getElementsByClassName("contri"); const contributionSection = collection[0]; diff --git a/server/index.js b/server/index.js index e0d8c9eb..08e69457 100644 --- a/server/index.js +++ b/server/index.js @@ -31,6 +31,7 @@ import brRoutes from "./modules/br/br.routes.js"; import fileRoutes from "./modules/file/file.routes.js"; import folderRoutes from "./modules/folder/folder.routes.js"; import yearRoutes from "./modules/year/year.routes.js"; +import quizRoutes from "./modules/quizzes/quizzes.routes.js"; import links from "./links.js"; const app = express(); @@ -75,6 +76,7 @@ app.use("/api/br", brRoutes); app.use("/api/files", fileRoutes); app.use("/api/folder", folderRoutes); app.use("/api/year", yearRoutes); +app.use("/api/quiz", quizRoutes); app.use( "/homepage", diff --git a/server/modules/quizzes/quizzes.controller.js b/server/modules/quizzes/quizzes.controller.js new file mode 100644 index 00000000..01448fc5 --- /dev/null +++ b/server/modules/quizzes/quizzes.controller.js @@ -0,0 +1,70 @@ +import quizEventModel from "./quizzes.model.js"; + +export const createQuizEvent = async (req, res) => { + try { + const {eventName, eventDate, course,courseName} = req.body; + if(!eventName || !eventDate || !course){ + console.log("All fields are required"); + return res.status(400).json({message: "All fields are required", success: false}); + } + + const newQuizEvent = new quizEventModel({ + eventName, + eventDate, + course, + // courseName + }); + + const savedEvent = await newQuizEvent.save(); + res.status(201).json({ + success: true, + message: "Event Created Successfully", + data: savedEvent, + }); + } catch (error){ + console.log(error); + res.status(500).json({ + success: false, + message: "Error creating quiz event", + error: error.message, + }); + } +} +export const getQuizEvents = async (req,res) =>{ + try { + const quizEvents = await quizEventModel.find().sort({eventDate: 1}); + + res.status(200).json({message: "Quiz events retrieved successfully", success: true, data: quizEvents}); + } catch (error) { + res.status(500).json({ + success: false, + message: "Error retrieving quiz events", + error: error.message, + }); + } +} +export const deleteQuizEvent = async (req,res) => { + try { + const quizEvent = await quizEventModel.findByIdAndDelete(req.params.eventId); + console.log("Deleted Quiz"); + res.status(200).json({message: "Quiz event deleted successfully", success: true, data: quizEvent}); + } catch (error) { + res.status(500).json({ + success: false, + message: "Error deleting quiz event", + error: error.message, + }); + } +} +export const modifyQuizEvent = async (req,res) => { + try { + const quizEvent = await quizEventModel.findByIdAndUpdate(req.params.eventId, req.body, {new: true}); + res.status(200).json({message: "Quiz event modified successfully", success: true, data: quizEvent}); + } catch (error) { + res.status(500).json({ + success: false, + message: "Error modifying quiz event", + error: error.message, + }); + } +} \ No newline at end of file diff --git a/server/modules/quizzes/quizzes.model.js b/server/modules/quizzes/quizzes.model.js new file mode 100644 index 00000000..46b1d3ae --- /dev/null +++ b/server/modules/quizzes/quizzes.model.js @@ -0,0 +1,30 @@ +import mongoose from "mongoose"; + +const quizEventSchema = new mongoose.Schema({ + eventName: { + type: String, + required: true, + trim: true + }, + eventDate: { + type: Date, + required: true + }, + course: { + type: String, + required: true, + trim: true + }, + // courseName: { + // type: String, + // required: true, + // trim: true + // }, + createdAt: { + type: Date, + default: Date.now + } +}); + +const QuizEvent = mongoose.model('QuizEvent', quizEventSchema); +export default QuizEvent; diff --git a/server/modules/quizzes/quizzes.routes.js b/server/modules/quizzes/quizzes.routes.js new file mode 100644 index 00000000..f33a80a3 --- /dev/null +++ b/server/modules/quizzes/quizzes.routes.js @@ -0,0 +1,15 @@ +import express from "express"; +import { createQuizEvent, getQuizEvents,deleteQuizEvent,modifyQuizEvent } from "./quizzes.controller.js"; +import {isBR } from "../../middleware/isBR.js"; + +const router = express.Router(); + +// Create a new quiz event (BRs only) +router.post('/create', createQuizEvent); + +// Get all quiz events +router.get('/events', getQuizEvents); +router.delete('/delete/:eventId', deleteQuizEvent); +router.put('/modify/:eventId', modifyQuizEvent); + +export default router; From 40659474dabb5847f181e6101896cd68c1432f67 Mon Sep 17 00:00:00 2001 From: Atish <125atishprak@gmail.com> Date: Sat, 6 Sep 2025 01:35:00 +0530 Subject: [PATCH 4/6] quiz events new page, backend --- client/src/App.jsx | 2 + client/src/api/Quiz.js | 8 +- client/src/components/container/styles.scss | 1 + .../screens/dashboard/components/quizcard.jsx | 8 +- client/src/screens/dashboard/index.jsx | 13 +- client/src/screens/dashboard/styles.scss | 39 ++ client/src/screens/myquizzes/Delete.svg | 1 + client/src/screens/myquizzes/edit.svg | 1 + client/src/screens/myquizzes/index.jsx | 254 +++++++++++++ client/src/screens/myquizzes/styles.scss | 338 ++++++++++++++++++ server/index.js | 1 - 11 files changed, 658 insertions(+), 8 deletions(-) create mode 100644 client/src/screens/myquizzes/Delete.svg create mode 100644 client/src/screens/myquizzes/edit.svg create mode 100644 client/src/screens/myquizzes/index.jsx create mode 100644 client/src/screens/myquizzes/styles.scss diff --git a/client/src/App.jsx b/client/src/App.jsx index e094a200..9a2973bf 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -6,6 +6,7 @@ import LoadingPage from "./loading.jsx"; import { BrowserRouter as Router, Routes, Route, useNavigate } from "react-router-dom"; import PrivateRoutes from "./router_utils/PrivateRoutes"; import ProfilePage from "./screens/profile.js"; +import MyQuizzes from "./screens/myquizzes"; import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; @@ -90,6 +91,7 @@ const App = () => { }> } path="dashboard" exact /> } path="profile" exact /> + } path="myquizzes" exact /> } path="browse"> } path=":code"> diff --git a/client/src/api/Quiz.js b/client/src/api/Quiz.js index 12c9ac34..9fb89824 100644 --- a/client/src/api/Quiz.js +++ b/client/src/api/Quiz.js @@ -101,11 +101,15 @@ export const getQuizEvents = async () => { } }; -export const modifyQuizEvent = async (eventId) => { +export const modifyQuizEvent = async (eventId, eventData) => { try { const response = await fetch(`${server}/api/quiz/modify/${eventId}`, { method: 'PUT', - credentials: 'include' + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify(eventData) }); const data = await response.json(); if(!response.ok) { diff --git a/client/src/components/container/styles.scss b/client/src/components/container/styles.scss index 92bbdd17..51929b1e 100644 --- a/client/src/components/container/styles.scss +++ b/client/src/components/container/styles.scss @@ -1,5 +1,6 @@ .container { position: relative; + flex: 1; .container-content { max-width: 1400px; diff --git a/client/src/screens/dashboard/components/quizcard.jsx b/client/src/screens/dashboard/components/quizcard.jsx index af65884e..72375f7d 100644 --- a/client/src/screens/dashboard/components/quizcard.jsx +++ b/client/src/screens/dashboard/components/quizcard.jsx @@ -1,14 +1,18 @@ import React from "react"; import "./quizcard.scss"; -const QuizCard = ({ code, name, date, day, color }) => { +const QuizCard = ({ code, name, date, day, color,children }) => { return ( -
+ <> +
{code}
{name}
{date}
{day}
+ {children}
+ + ); }; diff --git a/client/src/screens/dashboard/index.jsx b/client/src/screens/dashboard/index.jsx index ff0d5098..03b93305 100644 --- a/client/src/screens/dashboard/index.jsx +++ b/client/src/screens/dashboard/index.jsx @@ -8,8 +8,6 @@ import SubHeading from "../../components/subheading"; import CourseCard from "./components/coursecard"; import ContributionBanner from "./components/contributionbanner"; import QuizCard from "./components/quizcard"; -import { getQuizEvents } from "../../api/Quiz"; -//import { getQuizEventByUserId, getQuizEvents } from "../../api/Quiz"; import Footer from "../../components/footer"; import FavouriteCard from "./components/favouritecard"; @@ -205,7 +203,16 @@ const Dashboard = () => {
{/* MY QUIZZES */} - +
+ + +
{quizzes.length > 0 ? ( diff --git a/client/src/screens/dashboard/styles.scss b/client/src/screens/dashboard/styles.scss index d35ea966..7666efbc 100644 --- a/client/src/screens/dashboard/styles.scss +++ b/client/src/screens/dashboard/styles.scss @@ -32,6 +32,33 @@ padding: 20px 0px; } +.quizzes-header { + display: flex; + justify-content: space-between; + align-items: center; + + .view-all-quizzes-btn { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.3); + color: white; + padding: 8px 16px; + font-family: "Bold"; + font-size: 0.9rem; + cursor: pointer; + transition: all 0.2s ease; + + &:hover { + background: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.5); + transform: translateY(-1px); + } + + &:active { + transform: translateY(0); + } + } +} + .no-fav-graphic { width: 100%; height: 350px; @@ -93,6 +120,18 @@ justify-content: space-between; padding: 0 5px; } + + .quizzes-header { + flex-direction: column; + align-items: flex-start; + gap: 10px; + + .view-all-quizzes-btn { + align-self: flex-end; + font-size: 0.8rem; + padding: 6px 12px; + } + } } @media screen and (max-width: 480px) { diff --git a/client/src/screens/myquizzes/Delete.svg b/client/src/screens/myquizzes/Delete.svg new file mode 100644 index 00000000..036a5745 --- /dev/null +++ b/client/src/screens/myquizzes/Delete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/screens/myquizzes/edit.svg b/client/src/screens/myquizzes/edit.svg new file mode 100644 index 00000000..9d7486d8 --- /dev/null +++ b/client/src/screens/myquizzes/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/screens/myquizzes/index.jsx b/client/src/screens/myquizzes/index.jsx new file mode 100644 index 00000000..06120a1e --- /dev/null +++ b/client/src/screens/myquizzes/index.jsx @@ -0,0 +1,254 @@ +import "./styles.scss"; +import Container from "../../components/container"; +import Heading from "../../components/heading"; +import Space from "../../components/space"; +import NavBar from "../../components/navbar"; +import SubHeading from "../../components/subheading"; +import Footer from "../../components/footer"; +import QuizCard from "../dashboard/components/quizcard"; +import { useSelector } from "react-redux"; +import { useEffect, useState } from "react"; +import { getColors } from "../../utils/colors"; +import { getQuizEvents, deleteQuizEvent, modifyQuizEvent } from "../../api/Quiz"; +import { toast } from "react-toastify"; +import { useNavigate } from "react-router-dom"; + +// Custom hook to detect mobile view +function useIsMobile() { + const [isMobile, setIsMobile] = useState(window.innerWidth <= 768); + useEffect(() => { + const handleResize = () => setIsMobile(window.innerWidth <= 768); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + return isMobile; +} + +const MyQuizzes = () => { + const [quizzes, setQuizzes] = useState([]); + const [loading, setLoading] = useState(true); + const [editingQuiz, setEditingQuiz] = useState(null); + const [editForm, setEditForm] = useState({ eventName: "", eventDate: "", course: "" }); + const user = useSelector((state) => state.user); + const navigate = useNavigate(); + const isMobile = useIsMobile(); + + useEffect(() => { + fetchQuizzes(); + }, []); + + const fetchQuizzes = async () => { + try { + setLoading(true); + const data = await getQuizEvents(); + console.log("Quizzes fetched:", data); + setQuizzes(data || []); + } catch (err) { + console.error("Error fetching quizzes:", err); + setQuizzes([]); + toast.error("Failed to fetch quizzes"); + } finally { + setLoading(false); + } + }; + + const handleDeleteQuiz = async (quizId, quizName) => { + if (!user.user?.isBR) { + toast.error("Only Branch Representatives can delete quizzes"); + return; + } + + if (window.confirm(`Are you sure you want to delete "${quizName}"?`)) { + try { + await deleteQuizEvent(quizId); + toast.success("Quiz deleted successfully"); + fetchQuizzes(); // Refresh the list + } catch (error) { + console.error("Error deleting quiz:", error); + toast.error("Failed to delete quiz"); + } + } + }; + + const handleEditQuiz = (quiz) => { + if (!user.user?.isBR) { + toast.error("Only Branch Representatives can edit quizzes"); + return; + } + + setEditingQuiz(quiz._id); + setEditForm({ + eventName: quiz.eventName, + eventDate: quiz.eventDate ? new Date(quiz.eventDate).toISOString().split('T')[0] : "", + course: quiz.course + }); + }; + + const handleSaveEdit = async () => { + if (!editForm.eventName || !editForm.eventDate || !editForm.course) { + toast.error("Please fill in all fields"); + return; + } + + try { + await modifyQuizEvent(editingQuiz, editForm); + toast.success("Quiz updated successfully"); + setEditingQuiz(null); + setEditForm({ eventName: "", eventDate: "", course: "" }); + fetchQuizzes(); // Refresh the list + } catch (error) { + console.error("Error updating quiz:", error); + toast.error("Failed to update quiz"); + } + }; + + const handleCancelEdit = () => { + setEditingQuiz(null); + setEditForm({ eventName: "", eventDate: "", course: "" }); + }; + + if (loading) { + return ( +
+ + + +
+
Loading quizzes...
+
+
+
+
+ ); + } + + return ( +
+ + + +
+ + +
+ + + {quizzes.length > 0 ? ( +
+ {[...quizzes] + .sort((a, b) => new Date(a.eventDate) - new Date(b.eventDate)) + .map((quiz, idx) => ( +
+ + {user.user?.isBR && ( +
+ { + e.stopPropagation(); + handleEditQuiz(quiz); + }} + title="Edit Quiz" + > + { + e.stopPropagation(); + handleDeleteQuiz(quiz._id, quiz.eventName); + }} + title="Delete Quiz" + > +
+ )} +
+
+ ))} +
+ ) : ( +
+
No quizzes scheduled
+
+ {user.user?.isBR + ? "Create quizzes from the dashboard to see them here" + : "Quizzes will appear here when they are scheduled" + } +
+
+ )} + + {/* Edit Modal */} + {editingQuiz && ( +
+
+
+ + +
+
+
+ + setEditForm({...editForm, eventName: e.target.value})} + placeholder="Enter quiz name" + /> +
+
+ + setEditForm({...editForm, course: e.target.value})} + placeholder="Enter course code" + /> +
+
+ + setEditForm({...editForm, eventDate: e.target.value})} + /> +
+
+
+ + +
+
+
+ )} +
+
+
+ ); +}; + +export default MyQuizzes; diff --git a/client/src/screens/myquizzes/styles.scss b/client/src/screens/myquizzes/styles.scss new file mode 100644 index 00000000..f7114ce9 --- /dev/null +++ b/client/src/screens/myquizzes/styles.scss @@ -0,0 +1,338 @@ + +.myquizzes-header { + text-align: center; + margin-bottom: 20px; +} + +.quizzes-container { + display: flex; + flex-wrap: wrap; + gap: 20px; + justify-content: flex-start; + align-items: flex-start; + + &.mobile { + flex-direction: column; + gap: 15px; + } + .quiz-card { + position: relative; + z-index: 1; + + &:hover .quiz-actions { + opacity: 1; + } + } + + .quiz-actions { + position: absolute; + top: 10px; + right: 10px; + display: flex; + gap: 8px; + opacity: 0; + transition: opacity 0.3s ease; + z-index: 10; + + .rename-tick { + background: url("./edit.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + width: 20px; + height: 20px; + cursor: pointer; + opacity: 0.8; + transition: opacity 0.2s ease-in-out; + + &:hover { + opacity: 1; + transform: scale(1.1); + } + } + + .delete { + background: url("./delete.svg"); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + width: 20px; + height: 20px; + cursor: pointer; + opacity: 0.8; + transition: opacity 0.2s ease-in-out; + + &:hover { + opacity: 1; + transform: scale(1.1); + } + } + } +} + + + +.no-quizzes { + text-align: center; + padding: 60px 20px; + + .no-quizzes-text { + font-family: "Bold"; + font-size: 1.5rem; + color: #888; + margin-bottom: 10px; + } + + .no-quizzes-subtext { + font-family: "Regular"; + font-size: 1rem; + color: #666; + max-width: 400px; + margin: 0 auto; + line-height: 1.5; + } +} + +.loading-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 200px; + + .loading-text { + font-family: "Bold"; + font-size: 1.2rem; + color: #888; + } +} + +// Edit Modal Styles +.edit-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + padding: 20px; +} + +.edit-modal { + background: white; + width: 100%; + max-width: 500px; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + animation: modalSlideIn 0.3s ease-out; +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(-20px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.edit-modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 24px; + border-bottom: 1px solid #e0e0e0; + + .close-btn { + background: none; + border: none; + font-size: 24px; + cursor: pointer; + color: #666; + padding: 0; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + transition: all 0.2s ease; + + &:hover { + background: #f5f5f5; + color: #333; + } + } +} + +.edit-modal-content { + padding: 24px; + + .form-group { + margin-bottom: 20px; + + label { + display: block; + font-family: "Bold"; + font-size: 0.9rem; + color: #333; + margin-bottom: 8px; + } + + input { + width: 100%; + padding: 12px 16px; + border: 1px solid rgba(0, 0, 0, 0.3); + font-size: 1rem; + font-family: "Regular"; + transition: border-color 0.2s ease; + box-sizing: border-box; + + &:focus { + outline: none; + border-color: #4f46e5; + } + + &::placeholder { + color: #999; + } + } + } +} + +.edit-modal-actions { + display: flex; + gap: 12px; + justify-content: flex-end; + padding: 20px 24px; + border-top: 1px solid #e0e0e0; + + button { + padding: 12px 24px; + font-family: "Bold"; + border: 1px solid rgba(255, 255, 255, 0.33); + font-size: 0.9rem; + cursor: pointer; + transition: all 0.2s ease; + min-width: 100px; + + &.cancel-btn { + background: rgba(0,0,0,0.33); + color: black; + + &:hover { + background: rgba(0,0,0,0.33); + color: black; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } + } + + &.save-btn { + background: #fecf6f; + color: black; + + &:hover { + background: #fecf6f; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } + + &:active { + transform: translateY(0); + } + } + } +} + +// Mobile Responsive Styles +@media screen and (max-width: 768px) { + .quizzes-container { + gap: 15px; + + .quiz-item { + width: 100%; + + .quiz-actions { + opacity: 1; // Always show on mobile for better UX + position: static; + margin-top: 10px; + justify-content: center; + } + } + } + + .edit-modal { + margin: 10px; + max-height: 95vh; + + .edit-modal-header { + padding: 16px 20px; + } + + .edit-modal-content { + padding: 20px; + } + + .edit-modal-actions { + padding: 16px 20px; + flex-direction: column; + + button { + width: 100%; + } + } + } + + .no-quizzes { + padding: 40px 20px; + + .no-quizzes-text { + font-size: 1.3rem; + } + + .no-quizzes-subtext { + font-size: 0.9rem; + } + } +} + +@media screen and (max-width: 480px) { + .myquizzes-header { + margin-bottom: 15px; + } + + .quizzes-container { + gap: 12px; + } + + .edit-modal { + margin: 5px; + + .edit-modal-header { + padding: 12px 16px; + } + + .edit-modal-content { + padding: 16px; + + .form-group { + margin-bottom: 16px; + + input { + padding: 10px 14px; + font-size: 0.9rem; + } + } + } + + .edit-modal-actions { + padding: 12px 16px; + } + } +} diff --git a/server/index.js b/server/index.js index d4a2bffc..08e69457 100644 --- a/server/index.js +++ b/server/index.js @@ -33,7 +33,6 @@ import folderRoutes from "./modules/folder/folder.routes.js"; import yearRoutes from "./modules/year/year.routes.js"; import quizRoutes from "./modules/quizzes/quizzes.routes.js"; import links from "./links.js"; -import quizRoutes from "./modules/quizzes/quizzes.routes.js"; const app = express(); From 8f910d8963325afaad2dc8455c1235795f6f2863 Mon Sep 17 00:00:00 2001 From: arushi <2006arushi111@gmail.com> Date: Sat, 6 Sep 2025 11:51:10 +0530 Subject: [PATCH 5/6] create profname, update day countdown for quiz --- client/src/api/Quiz.js | 48 ++++++++ client/src/api/Year.js | 11 +- client/src/api/profName.js | 2 + .../browse/components/folder-info/index.jsx | 104 ++++++++++++++++++ .../browse/components/folder-info/styles.scss | 35 +++++- .../components/year-info/confirmDialog.jsx | 15 +++ .../browse/components/year-info/index.jsx | 6 + client/src/screens/browse/index.jsx | 1 + client/src/screens/browse/styles.scss | 59 +++++++++- .../dashboard/components/examcard/index.jsx | 4 +- client/src/screens/dashboard/index.jsx | 73 +++++++++++- client/yarn.lock | 10 ++ server/index.js | 3 + server/modules/course/course.model.js | 2 + .../modules/professor/professor.controller.js | 8 ++ server/modules/professor/professor.routes.js | 0 server/modules/quizzes/quizzes.controller.js | 44 ++++++++ server/modules/quizzes/quizzes.model.js | 25 +++++ server/modules/quizzes/quizzes.routes.js | 13 +++ server/modules/year/year.controller.js | 44 ++++++-- server/modules/year/year.routes.js | 7 +- server/yarn.lock | 5 + 22 files changed, 489 insertions(+), 30 deletions(-) create mode 100644 client/src/api/Quiz.js create mode 100644 client/src/api/profName.js create mode 100644 server/modules/professor/professor.controller.js create mode 100644 server/modules/professor/professor.routes.js create mode 100644 server/modules/quizzes/quizzes.controller.js create mode 100644 server/modules/quizzes/quizzes.model.js create mode 100644 server/modules/quizzes/quizzes.routes.js diff --git a/client/src/api/Quiz.js b/client/src/api/Quiz.js new file mode 100644 index 00000000..93749368 --- /dev/null +++ b/client/src/api/Quiz.js @@ -0,0 +1,48 @@ +import server from "./server"; + +export const createQuizEvent = async (eventName, eventDate, course) => { + try { + const response = await fetch(`${server}/api/quiz/create`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + eventName, + eventDate, + course + }) + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Failed to create quiz event'); + } + + return data; + } catch (error) { + throw error; + } +}; + +export const getAllQuizEvents = async () => { + try { + const response = await fetch(`${server}/api/quiz/events`, { + method: 'GET', + credentials: 'include' + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.message || 'Failed to fetch quiz events'); + } + console.log(data.data); + + return data.data; + } catch (error) { + throw error; + } +}; diff --git a/client/src/api/Year.js b/client/src/api/Year.js index bf108151..95dcccd4 100644 --- a/client/src/api/Year.js +++ b/client/src/api/Year.js @@ -15,10 +15,11 @@ API.interceptors.request.use((req) => { return req; }); -export const addYear = async ({ name, course }) => { +export const addYear = async ({ name, course, profName }) => { const { data } = await API.post("/year", { name, course, + profName, childType: "Folder", Children: [], }); @@ -31,3 +32,11 @@ export const deleteYear = async ({ folder, courseCode }) => { }); return data; }; + +export const editProfName = async ({ yearId, profName }) => { + const { data } = await API.put("/year/edit-prof", { + yearId, + profName, + }); + return data; +}; diff --git a/client/src/api/profName.js b/client/src/api/profName.js new file mode 100644 index 00000000..85e78631 --- /dev/null +++ b/client/src/api/profName.js @@ -0,0 +1,2 @@ +import axios from "axios"; +import serverRoot from "./server"; diff --git a/client/src/screens/browse/components/folder-info/index.jsx b/client/src/screens/browse/components/folder-info/index.jsx index f4bb11eb..2b111225 100644 --- a/client/src/screens/browse/components/folder-info/index.jsx +++ b/client/src/screens/browse/components/folder-info/index.jsx @@ -6,6 +6,7 @@ import Share from "../../../share"; import { useState } from "react"; import { createFolder } from "../../../../api/Folder"; import { getCourse } from "../../../../api/Course"; +import { editProfName } from "../../../../api/Year"; import { ChangeCurrentCourse, ChangeCurrentYearData, @@ -25,6 +26,7 @@ const FolderInfo = ({ isBR, path, name, + prof, canDownload, contributionHandler, folderId, @@ -35,7 +37,9 @@ const FolderInfo = ({ const currYear = useSelector((state) => state.fileBrowser.currentYear); const currentData = useSelector((state) => state.fileBrowser.currentData); const [showConfirm, setShowConfirm] = useState(false); + const [showEditProf, setShowEditProf] = useState(false); const [newFolderName, setNewFolderName] = useState(""); + const [editProfNameValue, setEditProfNameValue] = useState(""); const [childType, setChildType] = useState("File"); const [isAdding, setIsAdding] = useState(false); const [isDownloading, setIsDownloading] = useState(false); @@ -236,6 +240,37 @@ const FolderInfo = ({ setIsDownloading(false); } }; + + const handleEditProf = () => { + setEditProfNameValue(prof || ""); + setShowEditProf(true); + }; + + const handleConfirmEditProf = async () => { + if (!folderId) return; + + try { + await editProfName({ + yearId: folderId, + profName: editProfNameValue.trim(), + }); + + // Update the local state by refreshing the folder data + dispatch(RefreshCurrentFolder()); + + toast.success("Professor name updated successfully!"); + } catch (error) { + toast.error(error.response?.data?.message || "Failed to update professor name"); + } + + setShowEditProf(false); + setEditProfNameValue(""); + }; + + const cancelEditProf = () => { + setShowEditProf(false); + setEditProfNameValue(""); + }; return ( <>
@@ -243,6 +278,16 @@ const FolderInfo = ({

{path}

{name}

+
+ {prof && (

Prof. {prof}

)} + {isBR && !isReadOnlyCourse && ( +
+ )} +
{folderId && courseCode && ( <> @@ -314,6 +359,65 @@ const FolderInfo = ({ onCancel={() => setShowConfirm(false)} /> )} + + {/* Edit Professor Name Dialog (styled like Add Year) */} + {showEditProf && ( +
+
+
📁 Edit Professor
+
Update the professor name for this year
+ +
+ + setEditProfNameValue(e.target.value)} + placeholder="Enter professor name" + /> +
+ +
+
+ CANCEL +
+
+ SAVE +
+
+
+
+ )} ); }; diff --git a/client/src/screens/browse/components/folder-info/styles.scss b/client/src/screens/browse/components/folder-info/styles.scss index 308fca32..5cc1592e 100644 --- a/client/src/screens/browse/components/folder-info/styles.scss +++ b/client/src/screens/browse/components/folder-info/styles.scss @@ -27,6 +27,39 @@ .folder-name { font-family: "Bold"; font-size: 1.5rem; + + } + + .prof-name { + font-family: "Regular"; + font-size: 1rem; + padding-left: 5px; + color: #333; + } + + .prof-container { + display: flex; + align-items: center; + gap: 8px; + + .edit-prof-btn { + display: inline-block; + background: url(../browsefolder/edit.svg), none; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + height: 16px; + width: 16px; + margin-left: 5px; + vertical-align: middle; + cursor: pointer; + opacity: 1; // always visible + transition: transform 0.15s ease-in-out; + } + + .edit-prof-btn:hover { + transform: scale(1.06); + } } .folder-actions { @@ -216,4 +249,4 @@ #bottommarginneeded { margin-bottom: 2rem; -} +} \ No newline at end of file diff --git a/client/src/screens/browse/components/year-info/confirmDialog.jsx b/client/src/screens/browse/components/year-info/confirmDialog.jsx index 687d5438..8fb66667 100644 --- a/client/src/screens/browse/components/year-info/confirmDialog.jsx +++ b/client/src/screens/browse/components/year-info/confirmDialog.jsx @@ -22,6 +22,8 @@ const ConfirmDialog = ({ show, yearName = "", onYearNameChange = () => {}, + profName= "", + onProfNameChange = () => {}, onCancel, onConfirm, course, @@ -61,6 +63,19 @@ const ConfirmDialog = ({ />
+
+ + onProfNameChange(e.target.value)} + placeholder="Enter professor name" + /> +
diff --git a/client/src/screens/browse/components/year-info/index.jsx b/client/src/screens/browse/components/year-info/index.jsx index 4cb893a3..85949c2c 100644 --- a/client/src/screens/browse/components/year-info/index.jsx +++ b/client/src/screens/browse/components/year-info/index.jsx @@ -20,11 +20,13 @@ const YearInfo = ({ courseCode, course, // years list currYear, + }) => { const dispatch = useDispatch(); const [showConfirm, setShowConfirm] = useState(false); const [showConfirmDel, setShowConfirmDel] = useState(false); const [newYearName, setNewYearName] = useState(""); + const [newprofName, setNewProfName] = useState(""); const [isAddingYear, setIsAddingYear] = useState(false); const user = useSelector((state) => state.user.user); const isReadOnlyCourse = user?.readOnly?.some( @@ -48,6 +50,7 @@ const YearInfo = ({ if (isAddingYear) return; setIsAddingYear(true); const yearName = newYearName.trim(); + const profName = newprofName.trim(); if (!yearName) { setIsAddingYear(false); @@ -75,6 +78,7 @@ const YearInfo = ({ const newYear = await addYear({ name: yearName.trim(), course: courseCode, + profName: profName.trim(), }); course.push(newYear); @@ -179,6 +183,8 @@ const YearInfo = ({ // onInputChange={(e) => setNewFolderName(e.target.value)} yearName={newYearName} onYearNameChange={setNewYearName} + profName={newprofName} + onProfNameChange={setNewProfName} onConfirm={handleConfirmAddYear} onCancel={() => setShowConfirm(false)} confirmText="Create" diff --git a/client/src/screens/browse/index.jsx b/client/src/screens/browse/index.jsx index f8843457..2c2fe174 100644 --- a/client/src/screens/browse/index.jsx +++ b/client/src/screens/browse/index.jsx @@ -534,6 +534,7 @@ const BrowseScreen = () => { isBR={user.user.isBR} path={folderData?.path ? folderData.path : HeaderText} name={folderData?.name ? folderData.name : HeaderText} + prof={folderData?.profName ? folderData.profName : ""} canDownload={folderData?.childType === "File"} contributionHandler={contributionHandler} folderId={folderData?._id} diff --git a/client/src/screens/browse/styles.scss b/client/src/screens/browse/styles.scss index d8485cc4..d9486347 100644 --- a/client/src/screens/browse/styles.scss +++ b/client/src/screens/browse/styles.scss @@ -17,10 +17,12 @@ height: 1vh; // Extremely thin for very small screens } } + .controller { &::-webkit-scrollbar { display: none; } + display: flex; height: 92vh; // Keep original desktop height @@ -36,11 +38,13 @@ @media (max-width: 480px) { height: 98.8vh; // Compensate for 1.2vh navbar } + .left { flex: 3; max-width: 300px; border-right: 1px solid rgba(0, 0, 0, 0.33); overflow-y: auto; + .heading { padding: 10px 20px; font-family: "Bold"; @@ -48,6 +52,7 @@ background-color: #fecf6f; } } + .middle { flex: 6; border-right: 1px solid rgba(0, 0, 0, 0.33); @@ -59,26 +64,32 @@ flex-wrap: wrap; // justify-content: space-between; } + .empty-message { font-size: 1.2rem; } + &::-webkit-scrollbar { display: none; } } + .right { flex: 1; max-width: 200px; background-color: #000; color: #fff; + .year-content { display: flex; flex-direction: column-reverse; + .year-title { padding: 10px 20px; font-family: "Bold"; font-size: 1.2rem; } + .year { cursor: pointer; padding: 10px 20px; @@ -87,9 +98,11 @@ display: flex; align-items: center; justify-content: space-between; + &.selected { color: #000; background-color: #fff; + .delete { background: url(./components/browsefolder/Delete.svg), none; @@ -109,10 +122,12 @@ border-radius: 2px; } } + &.selected:hover .delete { opacity: 1; } } + &.add-year { border-top: 1px solid #333; margin-top: 10px; @@ -245,11 +260,9 @@ box-shadow: 0 0 0 4px rgba(254, 207, 111, 0.12), 0 4px 12px rgba(254, 207, 111, 0.15); transform: translateY(-2px); // More pronounced lift - background: linear-gradient( - 135deg, - #ffffff 0%, - #fffbf7 100% - ); // Warm focus background + background: linear-gradient(135deg, + #ffffff 0%, + #fffbf7 100%); // Warm focus background } &:hover:not(:disabled) { @@ -502,3 +515,39 @@ margin-bottom: 0; } } + +// ...existing code... + +.input_profname { + width: 100%; + padding: 10px 12px; + border: 1px solid black; + border-radius: 0; + background-color: #fff; + font-size: 1rem; + color: #333; + margin-top: 6px; + margin-bottom: 12px; + box-sizing: border-box; + transition: border-color 0.2s; + + &:focus { + outline: none; + border-color: #fecf6f; + box-shadow: 0 0 0 2px rgba(254, 207, 111, 0.2); + } + + &::placeholder { + color: black; + font-style: italic; + + } +} + +.label_section { + font-weight: 600; + margin-bottom: 4px; + color: #333; + font-size: 0.95rem; + display: block; +} \ No newline at end of file diff --git a/client/src/screens/dashboard/components/examcard/index.jsx b/client/src/screens/dashboard/components/examcard/index.jsx index 9b8871bf..f2b4a205 100644 --- a/client/src/screens/dashboard/components/examcard/index.jsx +++ b/client/src/screens/dashboard/components/examcard/index.jsx @@ -1,7 +1,7 @@ import "./styles.scss"; -const ExamCard = ({ days, name, color }) => { +const ExamCard = ({ days, name, color, onClick=()=>{} }) => { return ( -
+

{days ? days : 0}

diff --git a/client/src/screens/dashboard/index.jsx b/client/src/screens/dashboard/index.jsx index b9b8f431..9bd75814 100644 --- a/client/src/screens/dashboard/index.jsx +++ b/client/src/screens/dashboard/index.jsx @@ -9,6 +9,7 @@ import CourseCard from "./components/coursecard"; import ContributionBanner from "./components/contributionbanner"; import Footer from "../../components/footer"; import FavouriteCard from "./components/favouritecard"; +import { getAllQuizEvents } from "../../api/Quiz"; import { ChangeCurrentCourse, ResetFileBrowserState } from "../../actions/filebrowser_actions"; import { useDispatch, useSelector } from "react-redux"; @@ -16,7 +17,7 @@ import { useNavigate } from "react-router-dom"; import formatName from "../../utils/formatName"; import formatBranch from "../../utils/formatBranch"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useRef } from "react"; import { getColors } from "../../utils/colors"; import { LoadCourses } from "../../actions/filebrowser_actions"; import Contributions from "../contributions"; @@ -32,6 +33,13 @@ const Dashboard = () => { const [midSem, setMidSem] = useState(0); const [endSem, setEndSem] = useState(0); + const [quizzes, setQuizzes] = useState([]); + const [quizDate, setquizdate] = useState(0); + const targetRef = useRef(null); + + const handleScroll = () => { + targetRef.current?.scrollIntoView({ behavior: "smooth" }); + }; const contributionHandler = (event) => { const collection = document.getElementsByClassName("contri"); @@ -102,6 +110,53 @@ const Dashboard = () => { } run(); }, []); + // + useEffect(() => { + const fetchQuizzes = async () => { + try { + const all_quizzes = await getAllQuizEvents(); + if (!all_quizzes) { + console.log("Quizzes error fetching"); + return; + } + + console.log(all_quizzes); + const now = Date.now(); + + const user_quizzes = all_quizzes.filter((quiz) =>{ + if(user?.user?.courses.find(course => course.code === quiz.course)){ + if (new Date(quiz.eventDate).getTime() >= now){ + return true; + } + else + return false; + + }} + ) + //user_quizzes.find().sort({eventDate:1}) + setQuizzes(user_quizzes); + + } catch (error) { + console.error("Error fetching quizzes:", error); + } + }; + + fetchQuizzes(); + }, [user]); + useEffect(() => { + if (quizzes.length > 0 && quizzes[0]?.eventDate) { + const now = Date.now(); + + const daysTillQuiz = Math.ceil( + ((new Date(quizzes[0].eventDate).getTime()) - now) / (1000 * 3600 * 24) + ); + console.log("daystill quiz:", daysTillQuiz); + setquizdate(daysTillQuiz); + } + }, [quizzes]); + + + const handleClick = (code) => { let Code = code.replaceAll(" ", ""); @@ -137,16 +192,22 @@ const Dashboard = () => {
- {midSem >= 0 && ( + {/* {midSem >= 0 && ( - )} - {endSem >= 0 && ( - - )} + )} */} + {(quizzes.length && quizDate > 0) && + () + } + + {midSem > 0 ? + () : + () + }
+
{user.user.courses.map((course, index) => ( diff --git a/client/yarn.lock b/client/yarn.lock index bbc033e4..a4ff28eb 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -492,6 +492,11 @@ electron-to-chromium@^1.4.251: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz" integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== +esbuild-darwin-arm64@0.15.16: + version "0.15.16" + resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.16.tgz" + integrity sha512-fMXaUr5ou0M4WnewBKsspMtX++C1yIa3nJ5R2LSbLCfJT3uFdcRoU/NZjoM4kOMKyOD9Sa/2vlgN8G07K3SJnw== + esbuild@^0.15.9: version "0.15.16" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.15.16.tgz" @@ -561,6 +566,11 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" diff --git a/server/index.js b/server/index.js index e0d8c9eb..740111e0 100644 --- a/server/index.js +++ b/server/index.js @@ -31,8 +31,10 @@ import brRoutes from "./modules/br/br.routes.js"; import fileRoutes from "./modules/file/file.routes.js"; import folderRoutes from "./modules/folder/folder.routes.js"; import yearRoutes from "./modules/year/year.routes.js"; +import quizRoutes from "./modules/quizzes/quizzes.routes.js"; import links from "./links.js"; + const app = express(); const PORT = config.port; @@ -75,6 +77,7 @@ app.use("/api/br", brRoutes); app.use("/api/files", fileRoutes); app.use("/api/folder", folderRoutes); app.use("/api/year", yearRoutes); +app.use("/api/quiz",quizRoutes); app.use( "/homepage", diff --git a/server/modules/course/course.model.js b/server/modules/course/course.model.js index 6564de0f..aba99c05 100644 --- a/server/modules/course/course.model.js +++ b/server/modules/course/course.model.js @@ -3,6 +3,7 @@ import { model, Schema } from "mongoose"; const FolderSchema = Schema({ course: { type: String, required: true }, //id: { type: String, required: true }, + profName: {type:String, }, name: { type: String, required: true }, childType: { type: String, enum: ["File", "Folder"], required: true }, //path: { type: String, required: true }, @@ -28,6 +29,7 @@ const CourseSchema = Schema( code: { type: String, required: true, unique: true }, children: { type: [{ type: Schema.Types.ObjectId, ref: "Folder" }], default: [] }, books: [{ type: String }], + quiz: {} }, { timestamps: true } ); diff --git a/server/modules/professor/professor.controller.js b/server/modules/professor/professor.controller.js new file mode 100644 index 00000000..bd9cbb11 --- /dev/null +++ b/server/modules/professor/professor.controller.js @@ -0,0 +1,8 @@ +import CourseModel from "../course/course.model.js"; + + +async function addName(req,res) { + const {year,name,course}= req.body; + + +} \ No newline at end of file diff --git a/server/modules/professor/professor.routes.js b/server/modules/professor/professor.routes.js new file mode 100644 index 00000000..e69de29b diff --git a/server/modules/quizzes/quizzes.controller.js b/server/modules/quizzes/quizzes.controller.js new file mode 100644 index 00000000..bc04b1b3 --- /dev/null +++ b/server/modules/quizzes/quizzes.controller.js @@ -0,0 +1,44 @@ +import quizEventModel from "./quizzes.model.js"; + +export const createQuizEvent = async (req, res) => { + try { + const {eventName, eventDate, course} = req.body; + if(!eventName || !eventDate || !course){ + console.log("All fields are required"); + return res.status(400).json({message: "All fields are required", success: false}); + } + + const newQuizEvent = new quizEventModel({ + eventName, + eventDate, + course, + }); + + const savedEvent = await newQuizEvent.save(); + res.status(201).json({ + success: true, + message: "Event Created Successfully", + data: savedEvent, + }); + } catch (error){ + console.log(error); + res.status(500).json({ + success: false, + message: "Error creating quiz event", + error: error.message, + }); + } +} +export const getQuizEvents = async (req,res) =>{ + try { + const quizEvents = await quizEventModel.find().sort({eventDate: 1}); + + res.status(200).json({message: "Quiz events retrieved successfully", success: true, data: quizEvents}); + } catch (error) { + res.status(500).json({ + success: false, + message: "Error retrieving quiz events", + error: error.message, + }); + } +} diff --git a/server/modules/quizzes/quizzes.model.js b/server/modules/quizzes/quizzes.model.js new file mode 100644 index 00000000..2b6004b8 --- /dev/null +++ b/server/modules/quizzes/quizzes.model.js @@ -0,0 +1,25 @@ +import mongoose from "mongoose"; + +const quizEventSchema = new mongoose.Schema({ + eventName: { + type: String, + required: true, + trim: true + }, + eventDate: { + type: Date, + required: true + }, + course: { + type: String, + required: true, + trim: true + }, + createdAt: { + type: Date, + default: Date.now + } +}); + +const QuizEvent = mongoose.model('QuizEvent', quizEventSchema); +export default QuizEvent; diff --git a/server/modules/quizzes/quizzes.routes.js b/server/modules/quizzes/quizzes.routes.js new file mode 100644 index 00000000..9e8dddc5 --- /dev/null +++ b/server/modules/quizzes/quizzes.routes.js @@ -0,0 +1,13 @@ +import express from "express"; +import { createQuizEvent, getQuizEvents } from "./quizzes.controller.js"; +import {isBR } from "../../middleware/isBR.js"; + +const router = express.Router(); + +// Create a new quiz event (BRs only) +router.post('/create', createQuizEvent); + +// Get all quiz events +router.get('/events', getQuizEvents); + +export default router; diff --git a/server/modules/year/year.controller.js b/server/modules/year/year.controller.js index 8d7c0605..081daf38 100644 --- a/server/modules/year/year.controller.js +++ b/server/modules/year/year.controller.js @@ -3,16 +3,17 @@ import CourseModel from "../course/course.model.js"; import { deleteFile } from "../file/file.controller.js"; async function addYear(req, res) { - const { name, course} = req.body; + const { name, course, profName } = req.body; const newYear = await FolderModel.create({ name, course, + profName, children: [], - childType:"Folder" + childType: "Folder" }); if (course) { - const parent = await CourseModel.findOne({code:course}); + const parent = await CourseModel.findOne({ code: course }); parent.children.push(newYear._id); await parent.save(); } @@ -20,6 +21,25 @@ async function addYear(req, res) { return res.json(newYear); } +async function editProfName(req, res) { + const { yearId, profName } = req.body; + + try { + const year = await FolderModel.findById(yearId); + if (!year) { + return res.status(404).json({ message: "Year not found" }); + } + + // Allow editing professor names regardless of whether they exist or not + year.profName = profName.trim(); + await year.save(); + + return res.json({ message: "Professor name updated successfully", year }); + } catch (error) { + return res.status(500).json({ message: "Failed to update professor name" }); + } +} + async function deleteYear(req, res) { const { folder, courseCode } = req.body; const folderId = folder._id; @@ -27,8 +47,8 @@ async function deleteYear(req, res) { try { if (courseCode) { await CourseModel.findOneAndUpdate( - {code: courseCode}, - {$pull: { children: folderId }} + { code: courseCode }, + { $pull: { children: folderId } } ); } // const deleted = await FolderModel.findByIdAndDelete(folderId); @@ -44,19 +64,19 @@ async function deleteYear(req, res) { } } -async function recursiveDelete(folder){ - if(!folder.children) { +async function recursiveDelete(folder) { + if (!folder.children) { await FolderModel.findByIdAndDelete(folder._id); return; } - if (folder.childType === "Folder"){ - for(const subfolder of folder.children){ + if (folder.childType === "Folder") { + for (const subfolder of folder.children) { await recursiveDelete(subfolder); } await FolderModel.findByIdAndDelete(folder._id); } - else if(folder.childType === "File"){ - for(const file of folder.children){ + else if (folder.childType === "File") { + for (const file of folder.children) { console.log(file); await deleteFile(file); } @@ -64,4 +84,4 @@ async function recursiveDelete(folder){ } } -export {addYear,deleteYear} \ No newline at end of file +export { addYear, deleteYear, editProfName } \ No newline at end of file diff --git a/server/modules/year/year.routes.js b/server/modules/year/year.routes.js index 72dd3358..6a129f37 100644 --- a/server/modules/year/year.routes.js +++ b/server/modules/year/year.routes.js @@ -1,11 +1,12 @@ import express from "express"; -import {addYear,deleteYear} from "./year.controller.js"; +import { addYear, deleteYear, editProfName } from "./year.controller.js"; import isAuthenticated from "../../middleware/isAuthenticated.js"; -import {isBR} from "../../middleware/isBR.js"; +import { isBR } from "../../middleware/isBR.js"; const router = express.Router(); router.post("", isAuthenticated, isBR, addYear); -router.delete("/delete",isAuthenticated,isBR,deleteYear); +router.delete("/delete", isAuthenticated, isBR, deleteYear); +router.put("/edit-prof", isAuthenticated, isBR, editProfName); export default router; diff --git a/server/yarn.lock b/server/yarn.lock index 586fa22f..4f24dbbb 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -2040,6 +2040,11 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + function-bind@^1.1.1, function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" From 639da7e2387a83fb7f26043dff20167035fe5e6a Mon Sep 17 00:00:00 2001 From: arushi <2006arushi111@gmail.com> Date: Sat, 6 Sep 2025 15:26:14 +0530 Subject: [PATCH 6/6] final merged code for quizzes and profname --- .../browse/components/folder-info/index.jsx | 124 +-------------- .../browse/components/folder-info/styles.scss | 25 --- .../quiz-schedule-modal/styles.scss | 146 +++++++++--------- client/src/screens/browse/styles.scss | 1 - .../screens/dashboard/components/quizcard.jsx | 4 +- .../dashboard/components/quizcard.scss | 6 +- client/src/screens/dashboard/index.jsx | 33 +--- client/src/screens/dashboard/styles.scss | 18 ++- client/src/screens/myquizzes/index.jsx | 77 ++++----- client/src/screens/myquizzes/styles.scss | 79 +++++----- server/index.js | 1 - .../modules/professor/professor.controller.js | 8 - server/modules/professor/professor.routes.js | 0 server/modules/quizzes/quizzes.model.js | 25 --- 14 files changed, 174 insertions(+), 373 deletions(-) delete mode 100644 server/modules/professor/professor.controller.js delete mode 100644 server/modules/professor/professor.routes.js diff --git a/client/src/screens/browse/components/folder-info/index.jsx b/client/src/screens/browse/components/folder-info/index.jsx index 8dc4a5fb..24819085 100644 --- a/client/src/screens/browse/components/folder-info/index.jsx +++ b/client/src/screens/browse/components/folder-info/index.jsx @@ -6,7 +6,6 @@ import Share from "../../../share"; import { useState } from "react"; import { createFolder } from "../../../../api/Folder"; import { getCourse } from "../../../../api/Course"; -import { editProfName } from "../../../../api/Year"; import { ChangeCurrentCourse, ChangeCurrentYearData, @@ -21,7 +20,6 @@ import server from "../../../../api/server"; import JSZip from "jszip"; import { saveAs } from "file-saver"; import { fetchFolder } from "../../../../api/Folder"; -import QuizScheduleModal from "../quiz-schedule-modal"; const FolderInfo = ({ isBR, @@ -38,13 +36,10 @@ const FolderInfo = ({ const currYear = useSelector((state) => state.fileBrowser.currentYear); const currentData = useSelector((state) => state.fileBrowser.currentData); const [showConfirm, setShowConfirm] = useState(false); - const [showEditProf, setShowEditProf] = useState(false); const [newFolderName, setNewFolderName] = useState(""); - const [editProfNameValue, setEditProfNameValue] = useState(""); const [childType, setChildType] = useState("File"); const [isAdding, setIsAdding] = useState(false); const [isDownloading, setIsDownloading] = useState(false); - const [showQuizModal, setShowQuizModal] = useState(false); const user = useSelector((state) => state.user.user); const isReadOnlyCourse = user?.readOnly?.some( @@ -242,37 +237,6 @@ const FolderInfo = ({ setIsDownloading(false); } }; - - const handleEditProf = () => { - setEditProfNameValue(prof || ""); - setShowEditProf(true); - }; - - const handleConfirmEditProf = async () => { - if (!folderId) return; - - try { - await editProfName({ - yearId: folderId, - profName: editProfNameValue.trim(), - }); - - // Update the local state by refreshing the folder data - dispatch(RefreshCurrentFolder()); - - toast.success("Professor name updated successfully!"); - } catch (error) { - toast.error(error.response?.data?.message || "Failed to update professor name"); - } - - setShowEditProf(false); - setEditProfNameValue(""); - }; - - const cancelEditProf = () => { - setShowEditProf(false); - setEditProfNameValue(""); - }; return ( <>
@@ -280,16 +244,7 @@ const FolderInfo = ({

{path}

{name}

-
- {prof && (

Prof. {prof}

)} - {isBR && !isReadOnlyCourse && ( -
- )} -
+ {prof && (

Prof. {prof}

)}
{folderId && courseCode && ( <> @@ -344,17 +299,6 @@ const FolderInfo = ({ )} - - {/* Schedule Quiz button - only for BRs */} - {!isReadOnlyCourse && isBR && ( - - )}
)}
@@ -372,72 +316,6 @@ const FolderInfo = ({ onCancel={() => setShowConfirm(false)} /> )} - - {/* Edit Professor Name Dialog (styled like Add Year) */} - {showEditProf && ( -
-
-
📁 Edit Professor
-
Update the professor name for this year
- -
- - setEditProfNameValue(e.target.value)} - placeholder="Enter professor name" - /> -
- -
-
- CANCEL -
-
- SAVE -
-
-
-
- )} - - {/* Quiz Schedule Modal */} - setShowQuizModal(false)} - courseCode={courseCode} - /> ); }; diff --git a/client/src/screens/browse/components/folder-info/styles.scss b/client/src/screens/browse/components/folder-info/styles.scss index 5cc1592e..87c051f0 100644 --- a/client/src/screens/browse/components/folder-info/styles.scss +++ b/client/src/screens/browse/components/folder-info/styles.scss @@ -37,31 +37,6 @@ color: #333; } - .prof-container { - display: flex; - align-items: center; - gap: 8px; - - .edit-prof-btn { - display: inline-block; - background: url(../browsefolder/edit.svg), none; - background-size: contain; - background-repeat: no-repeat; - background-position: center; - height: 16px; - width: 16px; - margin-left: 5px; - vertical-align: middle; - cursor: pointer; - opacity: 1; // always visible - transition: transform 0.15s ease-in-out; - } - - .edit-prof-btn:hover { - transform: scale(1.06); - } - } - .folder-actions { display: flex; align-items: center; diff --git a/client/src/screens/browse/components/quiz-schedule-modal/styles.scss b/client/src/screens/browse/components/quiz-schedule-modal/styles.scss index f66c0861..0e877bd1 100644 --- a/client/src/screens/browse/components/quiz-schedule-modal/styles.scss +++ b/client/src/screens/browse/components/quiz-schedule-modal/styles.scss @@ -4,7 +4,7 @@ left: 0; right: 0; bottom: 0; - background-color: rgba(0, 0, 0, 0.5); + background-color: rgba(0, 0, 0, 0.35); display: flex; justify-content: center; align-items: center; @@ -13,14 +13,15 @@ } .quiz-modal { - background: white; - border-radius: 12px; - padding: 24px; - width: 90%; - max-width: 500px; - max-height: 90vh; - overflow-y: auto; - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + background: #fff; + border-radius: 0px; + padding: 36px 32px 32px 32px; + width: 100%; + max-width: 480px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18); + display: flex; + flex-direction: column; + align-items: flex-start; animation: modalSlideIn 0.3s ease-out; } @@ -29,6 +30,7 @@ opacity: 0; transform: translateY(-20px) scale(0.95); } + to { opacity: 1; transform: translateY(0) scale(1); @@ -37,95 +39,86 @@ .quiz-modal-header { display: flex; - justify-content: space-between; align-items: center; - margin-bottom: 24px; - padding-bottom: 16px; - border-bottom: 1px solid #e5e7eb; + gap: 16px; + margin-bottom: 8px; + + .modal-icon { + width: 40px; + height: 40px; + margin-right: 8px; + display: inline-block; + vertical-align: middle; + } h3 { margin: 0; - font-size: 1.5rem; - font-weight: 600; - color: #111827; + font-size: 2rem; + font-weight: 700; + color: #111; + letter-spacing: -1px; + font-family: inherit; } +} - .close-button { - background: none; - border: none; - font-size: 1.5rem; - cursor: pointer; - color: #6b7280; - padding: 4px; - border-radius: 4px; - transition: all 0.2s; - - &:hover { - background-color: #f3f4f6; - color: #374151; - } - - &:disabled { - opacity: 0.5; - cursor: not-allowed; - } - } +.quiz-modal-subtitle { + color: #888; + font-size: 1.15rem; + margin-bottom: 28px; + margin-top: 2px; + font-family: inherit; } .quiz-modal-form { + width: 100%; + font-family: Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif; + .form-group { - margin-bottom: 20px; + margin-bottom: 24px; label { display: block; margin-bottom: 8px; - font-weight: 500; - color: #374151; - font-size: 0.875rem; + font-weight: 700; + color: #111; + font-size: 1.15rem; + letter-spacing: -1px; } input { width: 100%; padding: 12px 16px; - border: 1px solid #d1d5db; - border-radius: 8px; - font-size: 1rem; - transition: border-color 0.2s, box-shadow 0.2s; + border: 1px solid #202020d9; + border-radius: 4px; + font-size: 1.15rem; + font-family: inherit; + color: #222; + margin-bottom: 2px; &:focus { outline: none; - border-color: #3b82f6; - box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); - } - - &:disabled { - background-color: #f9fafb; - cursor: not-allowed; - } - - &[type="datetime-local"] { - font-family: inherit; + border-color: #fecf6f; + box-shadow: 0 0 0 2px rgba(254, 207, 111, 0.15); } } } .form-actions { display: flex; - gap: 12px; - justify-content: flex-end; - margin-top: 24px; - padding-top: 20px; - border-top: 1px solid #e5e7eb; + gap: 24px; + justify-content: flex-start; + margin-top: 32px; .btn { - padding: 12px 24px; - border-radius: 8px; - font-weight: 500; - font-size: 0.875rem; + padding: 20px 0; + width: 220px; + border-radius: 0; + font-weight: 700; + font-size: 1.25rem; cursor: pointer; - transition: all 0.2s; border: none; - min-width: 100px; + letter-spacing: 1px; + transition: background 0.2s, color 0.2s; &:disabled { opacity: 0.5; @@ -134,20 +127,21 @@ } .btn-secondary { - background-color: #f3f4f6; - color: #374151; + background-color: #444; + color: #111; &:hover:not(:disabled) { - background-color: #e5e7eb; + background-color: #222; + color: #fff; } } .btn-primary { - background-color: #3b82f6; - color: white; + background-color: #fecf6f; + color: #111; &:hover:not(:disabled) { - background-color: #2563eb; + background-color: #ffd98c; } } } @@ -156,9 +150,9 @@ // Mobile responsiveness @media (max-width: 640px) { .quiz-modal { - margin: 16px; - padding: 20px; - width: calc(100% - 32px); + margin: 8px; + padding: 16px; + width: calc(100% - 16px); } .quiz-modal-header h3 { @@ -172,4 +166,4 @@ width: 100%; } } -} +} \ No newline at end of file diff --git a/client/src/screens/browse/styles.scss b/client/src/screens/browse/styles.scss index d9486347..9afbc48d 100644 --- a/client/src/screens/browse/styles.scss +++ b/client/src/screens/browse/styles.scss @@ -516,7 +516,6 @@ } } -// ...existing code... .input_profname { width: 100%; diff --git a/client/src/screens/dashboard/components/quizcard.jsx b/client/src/screens/dashboard/components/quizcard.jsx index 72375f7d..0bd29ad1 100644 --- a/client/src/screens/dashboard/components/quizcard.jsx +++ b/client/src/screens/dashboard/components/quizcard.jsx @@ -1,14 +1,14 @@ import React from "react"; import "./quizcard.scss"; -const QuizCard = ({ code, name, date, day, color,children }) => { +const QuizCard = ({ code, name, date, time, color,children }) => { return ( <>
{code}
{name}
{date}
-
{day}
+
{time}
{children}
diff --git a/client/src/screens/dashboard/components/quizcard.scss b/client/src/screens/dashboard/components/quizcard.scss index a292b2d1..96eaa8b9 100644 --- a/client/src/screens/dashboard/components/quizcard.scss +++ b/client/src/screens/dashboard/components/quizcard.scss @@ -12,6 +12,7 @@ align-items: flex-start; justify-content: flex-start; } + .quizcard-code { background-color: #000; color: #fff; @@ -24,6 +25,7 @@ margin-top: 0; width: fit-content; } + .quizcard-name { color: #333; font-family: "Bold"; @@ -32,6 +34,7 @@ margin-bottom: 10px; width: 100%; } + .quizcard-date { color: #555; font-family: "Bold"; @@ -40,10 +43,11 @@ margin-bottom: 10px; width: 100%; } + .quizcard-day { color: #555; font-family: "Bold"; font-size: 0.95em; text-align: left; width: 100%; -} +} \ No newline at end of file diff --git a/client/src/screens/dashboard/index.jsx b/client/src/screens/dashboard/index.jsx index b4d369fe..c4e3a432 100644 --- a/client/src/screens/dashboard/index.jsx +++ b/client/src/screens/dashboard/index.jsx @@ -31,31 +31,13 @@ import { getQuizEvents } from "../../api/Quiz"; const Dashboard = () => { const [quizzes, setQuizzes] = useState([]); - useEffect(() => { - async function fetchQuizzes() { - try { - const data = await getQuizEvents(); - console.log("Quizzes fetched:", data); - setQuizzes(data || []); - } catch (err) { - setQuizzes([]); - } - } - fetchQuizzes(); - }, []); const dispatch = useDispatch(); const navigate = useNavigate(); const user = useSelector((state) => state.user); const [midSem, setMidSem] = useState(0); const [endSem, setEndSem] = useState(0); - const [quizzes, setQuizzes] = useState([]); const [quizDate, setquizdate] = useState(0); - const targetRef = useRef(null); - - const handleScroll = () => { - targetRef.current?.scrollIntoView({ behavior: "smooth" }); - }; const contributionHandler = (event) => { const collection = document.getElementsByClassName("contri"); @@ -212,7 +194,7 @@ const Dashboard = () => { )} */} {(quizzes.length && quizDate > 0) && - () + ({ navigate('/myquizzes')}} />) } {midSem > 0 ? @@ -223,7 +205,6 @@ const Dashboard = () => {
-
{user.user.courses.map((course, index) => ( @@ -289,14 +270,12 @@ const Dashboard = () => { ? new Date(quiz.eventDate).toLocaleDateString() : "" } - day={ + time={ quiz.eventDate - ? new Date(quiz.eventDate).toLocaleDateString( - "en-US", - { - weekday: "long", - } - ) + ? new Date(quiz.eventDate).toLocaleTimeString("en-US", { + hour: "2-digit", + minute: "2-digit", + }) : "" } color={getColors(idx)} diff --git a/client/src/screens/dashboard/styles.scss b/client/src/screens/dashboard/styles.scss index 7666efbc..16aaf664 100644 --- a/client/src/screens/dashboard/styles.scss +++ b/client/src/screens/dashboard/styles.scss @@ -24,6 +24,7 @@ align-items: center; justify-content: flex-start; gap: 0; + margin-bottom: 32px; // Add this for spacing below MY COURSES } .fav-container { @@ -36,7 +37,7 @@ display: flex; justify-content: space-between; align-items: center; - + .view-all-quizzes-btn { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.3); @@ -46,13 +47,13 @@ font-size: 0.9rem; cursor: pointer; transition: all 0.2s ease; - + &:hover { background: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.5); transform: translateY(-1px); } - + &:active { transform: translateY(0); } @@ -93,7 +94,7 @@ } // Profile section spacing improvements - .split > div:first-child { + .split>div:first-child { .heading { margin-bottom: 8px; line-height: 1.2; @@ -119,13 +120,14 @@ .coursecard-container { justify-content: space-between; padding: 0 5px; + margin-bottom: 24px; // Optionally, for mobile screens } - + .quizzes-header { flex-direction: column; align-items: flex-start; gap: 10px; - + .view-all-quizzes-btn { align-self: flex-end; font-size: 0.8rem; @@ -140,7 +142,7 @@ } // Enhanced profile section spacing for small screens - .split > div:first-child { + .split>div:first-child { .heading { margin-bottom: 6px; line-height: 1.1; @@ -208,4 +210,4 @@ @media screen and (max-width: 768px) { padding: 8px; } -} +} \ No newline at end of file diff --git a/client/src/screens/myquizzes/index.jsx b/client/src/screens/myquizzes/index.jsx index 06120a1e..d3320c21 100644 --- a/client/src/screens/myquizzes/index.jsx +++ b/client/src/screens/myquizzes/index.jsx @@ -124,14 +124,14 @@ const MyQuizzes = () => { return (
- +
-
@@ -150,38 +150,36 @@ const MyQuizzes = () => { ? new Date(quiz.eventDate).toLocaleDateString() : "" } - day={ + time={ quiz.eventDate - ? new Date(quiz.eventDate).toLocaleDateString( - "en-US", - { - weekday: "long", - } - ) + ? new Date(quiz.eventDate).toLocaleTimeString("en-US", { + hour: "2-digit", + minute: "2-digit", + }) : "" } color={getColors(idx)} > - {user.user?.isBR && ( -
- { - e.stopPropagation(); - handleEditQuiz(quiz); - }} - title="Edit Quiz" - > - { - e.stopPropagation(); - handleDeleteQuiz(quiz._id, quiz.eventName); - }} - title="Delete Quiz" - > -
- )} + {user.user?.isBR && ( +
+ { + e.stopPropagation(); + handleEditQuiz(quiz); + }} + title="Edit Quiz" + > + { + e.stopPropagation(); + handleDeleteQuiz(quiz._id, quiz.eventName); + }} + title="Delete Quiz" + > +
+ )}
))} @@ -190,7 +188,7 @@ const MyQuizzes = () => {
No quizzes scheduled
- {user.user?.isBR + {user.user?.isBR ? "Create quizzes from the dashboard to see them here" : "Quizzes will appear here when they are scheduled" } @@ -212,17 +210,22 @@ const MyQuizzes = () => { setEditForm({...editForm, eventName: e.target.value})} + onChange={(e) => setEditForm({ ...editForm, eventName: e.target.value })} placeholder="Enter quiz name" />
- + setEditForm({...editForm, course: e.target.value})} - placeholder="Enter course code" + readOnly + style={{ + backgroundColor: '#f5f5f5', + color: '#666', + cursor: 'not-allowed' + }} + placeholder="Course code cannot be changed" />
@@ -230,7 +233,7 @@ const MyQuizzes = () => { setEditForm({...editForm, eventDate: e.target.value})} + onChange={(e) => setEditForm({ ...editForm, eventDate: e.target.value })} />
diff --git a/client/src/screens/myquizzes/styles.scss b/client/src/screens/myquizzes/styles.scss index f7114ce9..1901b7ea 100644 --- a/client/src/screens/myquizzes/styles.scss +++ b/client/src/screens/myquizzes/styles.scss @@ -1,4 +1,3 @@ - .myquizzes-header { text-align: center; margin-bottom: 20px; @@ -10,11 +9,12 @@ gap: 20px; justify-content: flex-start; align-items: flex-start; - + &.mobile { flex-direction: column; gap: 15px; } + .quiz-card { position: relative; z-index: 1; @@ -75,14 +75,14 @@ .no-quizzes { text-align: center; padding: 60px 20px; - + .no-quizzes-text { font-family: "Bold"; font-size: 1.5rem; color: #888; margin-bottom: 10px; } - + .no-quizzes-subtext { font-family: "Regular"; font-size: 1rem; @@ -98,7 +98,7 @@ justify-content: center; align-items: center; min-height: 200px; - + .loading-text { font-family: "Bold"; font-size: 1.2rem; @@ -136,6 +136,7 @@ opacity: 0; transform: translateY(-20px) scale(0.95); } + to { opacity: 1; transform: translateY(0) scale(1); @@ -148,7 +149,7 @@ align-items: center; padding: 20px 24px; border-bottom: 1px solid #e0e0e0; - + .close-btn { background: none; border: none; @@ -163,7 +164,7 @@ justify-content: center; border-radius: 50%; transition: all 0.2s ease; - + &:hover { background: #f5f5f5; color: #333; @@ -173,10 +174,10 @@ .edit-modal-content { padding: 24px; - + .form-group { margin-bottom: 20px; - + label { display: block; font-family: "Bold"; @@ -184,7 +185,7 @@ color: #333; margin-bottom: 8px; } - + input { width: 100%; padding: 12px 16px; @@ -193,12 +194,12 @@ font-family: "Regular"; transition: border-color 0.2s ease; box-sizing: border-box; - + &:focus { outline: none; border-color: #4f46e5; } - + &::placeholder { color: #999; } @@ -212,37 +213,37 @@ justify-content: flex-end; padding: 20px 24px; border-top: 1px solid #e0e0e0; - + button { padding: 12px 24px; font-family: "Bold"; - border: 1px solid rgba(255, 255, 255, 0.33); + border: 1px solid rgba(255, 255, 255, 0.33); font-size: 0.9rem; cursor: pointer; transition: all 0.2s ease; min-width: 100px; - + &.cancel-btn { - background: rgba(0,0,0,0.33); + background: rgba(0, 0, 0, 0.33); color: black; - + &:hover { - background: rgba(0,0,0,0.33); + background: rgba(0, 0, 0, 0.33); color: black; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } } - + &.save-btn { background: #fecf6f; color: black; - + &:hover { background: #fecf6f; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } - + &:active { transform: translateY(0); } @@ -254,10 +255,10 @@ @media screen and (max-width: 768px) { .quizzes-container { gap: 15px; - + .quiz-item { width: 100%; - + .quiz-actions { opacity: 1; // Always show on mobile for better UX position: static; @@ -266,36 +267,36 @@ } } } - + .edit-modal { margin: 10px; max-height: 95vh; - + .edit-modal-header { padding: 16px 20px; } - + .edit-modal-content { padding: 20px; } - + .edit-modal-actions { padding: 16px 20px; flex-direction: column; - + button { width: 100%; } } } - + .no-quizzes { padding: 40px 20px; - + .no-quizzes-text { font-size: 1.3rem; } - + .no-quizzes-subtext { font-size: 0.9rem; } @@ -306,33 +307,33 @@ .myquizzes-header { margin-bottom: 15px; } - + .quizzes-container { gap: 12px; } - + .edit-modal { margin: 5px; - + .edit-modal-header { padding: 12px 16px; } - + .edit-modal-content { padding: 16px; - + .form-group { margin-bottom: 16px; - + input { padding: 10px 14px; font-size: 0.9rem; } } } - + .edit-modal-actions { padding: 12px 16px; } } -} +} \ No newline at end of file diff --git a/server/index.js b/server/index.js index 9f35c738..740111e0 100644 --- a/server/index.js +++ b/server/index.js @@ -32,7 +32,6 @@ import fileRoutes from "./modules/file/file.routes.js"; import folderRoutes from "./modules/folder/folder.routes.js"; import yearRoutes from "./modules/year/year.routes.js"; import quizRoutes from "./modules/quizzes/quizzes.routes.js"; -import quizRoutes from "./modules/quizzes/quizzes.routes.js"; import links from "./links.js"; diff --git a/server/modules/professor/professor.controller.js b/server/modules/professor/professor.controller.js deleted file mode 100644 index bd9cbb11..00000000 --- a/server/modules/professor/professor.controller.js +++ /dev/null @@ -1,8 +0,0 @@ -import CourseModel from "../course/course.model.js"; - - -async function addName(req,res) { - const {year,name,course}= req.body; - - -} \ No newline at end of file diff --git a/server/modules/professor/professor.routes.js b/server/modules/professor/professor.routes.js deleted file mode 100644 index e69de29b..00000000 diff --git a/server/modules/quizzes/quizzes.model.js b/server/modules/quizzes/quizzes.model.js index 949d797f..b2369a77 100644 --- a/server/modules/quizzes/quizzes.model.js +++ b/server/modules/quizzes/quizzes.model.js @@ -21,31 +21,6 @@ const quizEventSchema = new mongoose.Schema({ } }); -const QuizEvent = mongoose.model('QuizEvent', quizEventSchema); -export default QuizEvent; - -import mongoose from "mongoose"; - -const quizEventSchema = new mongoose.Schema({ - eventName: { - type: String, - required: true, - trim: true - }, - eventDate: { - type: Date, - required: true - }, - course: { - type: String, - required: true, - trim: true - }, - createdAt: { - type: Date, - default: Date.now - } -}); const QuizEvent = mongoose.model('QuizEvent', quizEventSchema); export default QuizEvent;