From 1b3c0d19399ee57d309da059d08da79604217eab Mon Sep 17 00:00:00 2001 From: shubhangiirawat Date: Sat, 18 Apr 2026 15:32:00 +0530 Subject: [PATCH 1/2] exam timetable during exam time --- src/components/Timetable/ExamTimetable.jsx | 10 ++++++---- src/components/Timetable/TimetableContainer.jsx | 11 +++++++++++ src/components/Timetable/examTimetableConfig.js | 17 +++++++++++++---- src/components/Timetable/index.jsx | 11 +++++++++++ src/pages/Timetable.jsx | 4 ++-- 5 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/components/Timetable/ExamTimetable.jsx b/src/components/Timetable/ExamTimetable.jsx index 05a9bfb..33a7d6d 100644 --- a/src/components/Timetable/ExamTimetable.jsx +++ b/src/components/Timetable/ExamTimetable.jsx @@ -12,6 +12,7 @@ import { EXAM_DATE_RANGE, EXAM_LABEL, EXAM_TIME_SLOTS, + isExamPeriod } from './examTimetableConfig' import ExamTimetableDownload from './ExamTimetableDownload' @@ -222,10 +223,11 @@ const Table = ({ timetable }) => { } const buildDisplayDatesFromRange = () => { - if (!EXAM_DATE_RANGE?.start || !EXAM_DATE_RANGE?.end) return [] + const range = Array.isArray(EXAM_DATE_RANGE) ? EXAM_DATE_RANGE[0] : EXAM_DATE_RANGE + if (!range?.start || !range?.end) return [] - const startDate = parseShortDate(EXAM_DATE_RANGE.start) - const endDate = parseShortDate(EXAM_DATE_RANGE.end) + const startDate = parseShortDate(range.start.split('-').reverse().join('/')) + const endDate = parseShortDate(range.end.split('-').reverse().join('/')) if (!startDate || !endDate || startDate > endDate) return [] const dates = [] @@ -558,7 +560,7 @@ const CourseFinderFilterForm = ({ setCoursesAndSlots }) => { } const PopupExample = () => { - const [isOpen, setIsOpen] = useState(false) + const [isOpen, setIsOpen] = useState(isExamPeriod()) const [timetable, setTimetable] = useState({}) const [courses, setCourses] = useState([]) const [slots, setSlots] = useState([]) diff --git a/src/components/Timetable/TimetableContainer.jsx b/src/components/Timetable/TimetableContainer.jsx index 3b124df..d896557 100644 --- a/src/components/Timetable/TimetableContainer.jsx +++ b/src/components/Timetable/TimetableContainer.jsx @@ -41,11 +41,21 @@ import ExamPlanner from './eplanner_Exam' import PersonalPlanner from './eplanner_personal' import ReminderPlanner from './eplanner_reminder' import EplannerAPI from './eplannerAPI' +import ExamTimetable from './ExamTimetable' +import { isExamPeriod } from './examTimetableConfig' import TimetableDownloadLink from './TimetableDownloadLink' import TimetableSearch from './TimetableSearch' import TimetableShareButton from './TimetableShareButton' +const TimetablePage = () => { + if (isExamPeriod()) { + return + } + return +} + const TimetableContainer = () => { + const dispatch = useDispatch() const semesterList = useSelector(selectSemesters) const courseAPILoading = useSelector(selectCourseAPILoading) @@ -76,6 +86,7 @@ const TimetableContainer = () => { const [dropdownVisible, setDropdownVisible] = useState(false) const [coursesModalVisible, setCoursesModalVisible] = useState(false) + // Eplanner events state const [eplannerEvents, setEplannerEvents] = useState({ personal: [], diff --git a/src/components/Timetable/examTimetableConfig.js b/src/components/Timetable/examTimetableConfig.js index 278aadf..37cdf88 100644 --- a/src/components/Timetable/examTimetableConfig.js +++ b/src/components/Timetable/examTimetableConfig.js @@ -1,7 +1,7 @@ -export const EXAM_DATE_RANGE = { - start: '20/04/26', - end: '01/05/26', -} +export const EXAM_DATE_RANGE =[ { + start: '2026-04-20', + end: '2026-05-01', +}] export const EXAM_LABEL = 'End-semester Examinations' export const EXAM_CALENDAR_NAME = 'End Semester Exams' @@ -27,3 +27,12 @@ export const EXAM_TIME_SLOTS = [ endTime: '20:30', }, ] +export const isExamPeriod = () => { + const today = new Date() + return EXAM_DATE_RANGE.some(({ start, end }) => { + const s = new Date(start) + const e = new Date(end) + e.setHours(23, 59, 59) // include the end day fully + return today >= s && today <= e + }) +} \ No newline at end of file diff --git a/src/components/Timetable/index.jsx b/src/components/Timetable/index.jsx index fdd6abe..c9acc6a 100644 --- a/src/components/Timetable/index.jsx +++ b/src/components/Timetable/index.jsx @@ -1,3 +1,14 @@ + +import ExamTimetable from './ExamTimetable' +import { isExamPeriod } from './examTimetableConfig' +import TimetableContainer from './TimetableContainer' + +const TimetablePage = () => { + if (isExamPeriod()) { + return + } + return +} export { default as TimetableContainer } from './TimetableContainer' export { default as TimetableCourseItem } from './TimetableCourseItem' export { default as TimetableShareContainer } from './TimetableShareContainer' diff --git a/src/pages/Timetable.jsx b/src/pages/Timetable.jsx index b65209c..3cc01d7 100644 --- a/src/pages/Timetable.jsx +++ b/src/pages/Timetable.jsx @@ -2,6 +2,7 @@ import { Helmet } from 'react-helmet-async' import { PageContainer } from 'components/shared' import { TimetableContainer } from 'components/Timetable' + // import ReminderPlanner from 'components/Timetable/eplanner_reminder' // import ExamPlanner from 'components/Timetable/eplanner_Exam' // import PersonalPlanner from 'components/Timetable/eplanner_personal' @@ -18,13 +19,12 @@ const TimeTable = () => { content="IIT Bombay time table for selected courses" /> - - {/* */} {/* Components now handled by TimetableContainer dropdown modals */} {/* */} {/* */} {/* */} + ) } From ad0117c8e4545ad8efa88bb45d468b88ea351140 Mon Sep 17 00:00:00 2001 From: shubhangiirawat Date: Tue, 19 May 2026 00:46:07 +0530 Subject: [PATCH 2/2] timetable during exams --- src/components/Timetable/ExamTimetable.jsx | 36 +- .../Timetable/TimetableContainer.jsx | 381 +++++++++++++++++- .../Timetable/examTimetableConfig.js | 4 +- src/components/Timetable/index.jsx | 10 - src/pages/Timetable.jsx | 24 +- 5 files changed, 393 insertions(+), 62 deletions(-) diff --git a/src/components/Timetable/ExamTimetable.jsx b/src/components/Timetable/ExamTimetable.jsx index 33a7d6d..1cf58da 100644 --- a/src/components/Timetable/ExamTimetable.jsx +++ b/src/components/Timetable/ExamTimetable.jsx @@ -42,12 +42,13 @@ if (!document.getElementById(montserratFontId)) { const styles = { container: { - padding: '1rem', + padding: '12px', fontFamily: 'Montserrat,Segoe UI, Tahoma, Geneva, Verdana, sans-serif', backgroundColor: '#2d2941ff ', borderRadius: '12px', JustifyContent: 'start', - marginTop: '1.2rem', + margin: '0px', + width:'550px' }, card: { backgroundColor: '#342f4bff ', @@ -327,7 +328,8 @@ const Table = ({ timetable }) => {
@@ -489,15 +491,12 @@ const Table = ({ timetable }) => { } const CourseFinderFilterForm = ({ setCoursesAndSlots }) => { - // const { deleteQueryString, getQueryString, setQueryString } = useQueryString() - // const [form] = Form.useForm() - // const [userTimetableCourses, setUserTimetableCourses] = useState([]) + const [semesters, setSemesters] = useState({}) const getSemesters = async () => { try { const response = await API.semesters.list() setSemesters(response[0]) - // console.log('Fetched timetable slots: here', response) } catch (error) { setSemesters({}) } @@ -559,8 +558,10 @@ const CourseFinderFilterForm = ({ setCoursesAndSlots }) => { return null } -const PopupExample = () => { - const [isOpen, setIsOpen] = useState(isExamPeriod()) +const PopupExample = ({ + setShowRegularTimetable, +}) => { + const [isOpen, setIsOpen] = useState(false) const [timetable, setTimetable] = useState({}) const [courses, setCourses] = useState([]) const [slots, setSlots] = useState([]) @@ -655,7 +656,14 @@ const PopupExample = () => { return (
- + { + if (isExamPeriod()) { + togglePopup() + } + }} +> End Sem @@ -736,10 +744,14 @@ const PopupExample = () => { ) } -export const Exam = () => { +export const Exam = ({ + setShowRegularTimetable, +}) => { return (
- +
) } diff --git a/src/components/Timetable/TimetableContainer.jsx b/src/components/Timetable/TimetableContainer.jsx index d896557..2ac3dcd 100644 --- a/src/components/Timetable/TimetableContainer.jsx +++ b/src/components/Timetable/TimetableContainer.jsx @@ -37,23 +37,22 @@ import { updateTimetable } from 'store/userSlice' import { makeGradient } from 'styles' import 'styles/CustomModal.css' + import ExamPlanner from './eplanner_Exam' import PersonalPlanner from './eplanner_personal' import ReminderPlanner from './eplanner_reminder' import EplannerAPI from './eplannerAPI' -import ExamTimetable from './ExamTimetable' -import { isExamPeriod } from './examTimetableConfig' +import { + EXAM_DATE_RANGE, + EXAM_LABEL, + EXAM_TIME_SLOTS, + isExamPeriod, +} from './examTimetableConfig' +import ExamTimetableDownload from './ExamTimetableDownload' import TimetableDownloadLink from './TimetableDownloadLink' import TimetableSearch from './TimetableSearch' import TimetableShareButton from './TimetableShareButton' -const TimetablePage = () => { - if (isExamPeriod()) { - return - } - return -} - const TimetableContainer = () => { const dispatch = useDispatch() @@ -62,11 +61,15 @@ const TimetableContainer = () => { const [courseTimetableList, setCourseTimetableList] = useState([]) const [courseData, setCourseData] = useState([]) + const [examTimetable, setExamTimetable] = useState({}) +const [examCourses, setExamCourses] = useState([]) +const [examSlots, setExamSlots] = useState([]) const [coursedata, setCoursedata] = useState({}) const [loading, setLoading] = useState(courseAPILoading) const [semIdx, setSemIdx] = useState(null) const [currentDate, setCurrentDate] = useState(moment()) const [loadingg, setLoadingg] = useState(true) + const [showRegularTimetable, setShowRegularTimetable] = useState(!isExamPeriod()) // const [reminderItems, ] const { getQueryString } = useQueryString() @@ -207,7 +210,8 @@ const TimetableContainer = () => { Personal - handleDropdownItemClick('exam')}> + { + handleDropdownItemClick('exam')}}> @@ -402,8 +406,7 @@ const TimetableContainer = () => { const courseList = courseTimetableList.map((item) => item.course) return courseList } - - useEffect(() => { + useEffect(() => { const fetchCourseData = async () => { try { setLoading(true) @@ -456,17 +459,16 @@ const TimetableContainer = () => { }) } } - item.lectureSlots.forEach((slotName) => createEventHandler(slotName, 'Lecture') ) item.tutorialSlots.forEach((slotName) => createEventHandler(slotName, 'Tutorial') - ) - }) - + )} + ) // Add eplanner events to the events array - const addEplannerEvents = () => { + + const addEplannerEvents = () => { // Helper to convert time to row index const timeToRow = (timeStr) => { if (!timeStr) return 0 @@ -713,13 +715,177 @@ const TimetableContainer = () => { } }) } + addEplannerEvents() + return events + } + const events = getEventsForView() +const parseISODate = (str) => { + // expects 'YYYY-MM-DD' + const [year, month, day] = str.split('-').map(Number) + return new Date(year, month - 1, day) +} - addEplannerEvents() +const buildAllDates = () => { + const range = Array.isArray(EXAM_DATE_RANGE) + ? EXAM_DATE_RANGE[0] + : EXAM_DATE_RANGE + if (!range?.start || !range?.end) return [] + + const start = parseISODate(range.start) + const end = parseISODate(range.end) + end.setHours(23, 59, 59) + + const months = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'] + const weekdays = ['SUN','MON','TUE','WED','THU','FRI','SAT'] + + const dates = [] + const cursor = new Date(start) + while (cursor <= end) { + dates.push({ + display: `${cursor.getDate()} ${months[cursor.getMonth()]}`, + weekday: weekdays[cursor.getDay()], + iso: cursor.toISOString().slice(0, 10), + }) + cursor.setDate(cursor.getDate() + 1) + } + return dates +} - return events +// The API returns dayDate strings like "Friday, 18/04/26" +// Convert to display key "18 APR" to match our date list +const toDisplayKey = (dateStr) => { + const afterComma = (dateStr.split(',')[1] || dateStr) + .replace(/\u00A0/g, ' ') + .trim() + const [rawDay = '', rawMonth = ''] = afterComma.split('/') + const dayNum = parseInt(String(rawDay).trim(), 10) + const monthNum = parseInt(String(rawMonth).trim(), 10) + const months = ['JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'] + const monthAbbr = months[(monthNum || 0) - 1] || '' + return `${Number.isFinite(dayNum) ? dayNum : ''} ${monthAbbr}`.trim() +} +useEffect(() => { + const filtered = courseTimetableList.filter((item) => { + const firstSlot = + Array.isArray(item.lectureSlots) && item.lectureSlots.length > 0 + ? item.lectureSlots[0] + : '' + + return !(typeof firstSlot === 'string' && firstSlot.startsWith('L')) + }) + + const courseArr = filtered.map((item) => item.course) + + const slotArr = filtered.map((item) => { + const firstSlot = + Array.isArray(item.lectureSlots) && item.lectureSlots.length > 0 + ? item.lectureSlots[0] + : '' + + if (!firstSlot) return 0 + + const match = firstSlot.match(/^\d+/) + + return match ? parseInt(match[0], 10) : 0 + }) + + setExamCourses(courseArr) + setExamSlots(slotArr) +}, [courseTimetableList]) +useEffect(() => { + console.log('examCourses:', examCourses) + console.log('isExamPeriod:', isExamPeriod()) + if (!isExamPeriod()) return undefined + + if (!examCourses.length) { + setExamTimetable({}) + return undefined } - const events = getEventsForView() + const timeoutId = setTimeout(async () => { + const userCourses = examCourses.map((code, idx) => { + const slotNumber = examSlots[idx] + + return slotNumber + ? { course_code: code, course_slot_number: slotNumber } + : { course_code: code } + }) + + try { + const res = await API.examSchedule.getBatch({ + courses: userCourses, + }) + + let scheduleRows = [] + + if (Array.isArray(res)) scheduleRows = res + else if (Array.isArray(res?.data)) scheduleRows = res.data + + const temp = {} + + const createEmptySlots = () => + Object.fromEntries( + EXAM_TIME_SLOTS.map(({ slot }) => [slot, []]) + ) + + scheduleRows + .filter((s) => s && !s.error) + .forEach((schedule) => { + const { dayDate, mappedSlot, courseCode } = schedule + + if (!dayDate || !mappedSlot || !courseCode) return + + if (!temp[dayDate]) { + temp[dayDate] = createEmptySlots() + } + + if ( + Object.prototype.hasOwnProperty.call( + temp[dayDate], + mappedSlot + ) + ) { + temp[dayDate][mappedSlot].push(courseCode) + } + }) + + setExamTimetable(temp) + } catch (err) { + toast({ + status: 'error', + content: 'Failed to fetch exam schedule', + }) + } + }, 300) + + return () => clearTimeout(timeoutId) +}, [examCourses, examSlots]) +const allDates = buildAllDates() + +const organized = {} + +allDates.forEach(({ display }) => { + organized[display] = Object.fromEntries( + EXAM_TIME_SLOTS.map(({ slot }) => [slot, []]) + ) +}) + +Object.entries(examTimetable).forEach(([dateStr, slotData]) => { + const displayKey = toDisplayKey(dateStr) + + if (!organized[displayKey]) return + + Object.entries(slotData).forEach(([slotNum, courseCodes]) => { + const slot = parseInt(slotNum, 10) + + if (organized[displayKey][slot]) { + courseCodes.forEach((code) => { + organized[displayKey][slot].push(code) + }) + } + }) +}) + const getDayViewDateDisplay = (date) => { return `${date.format('dddd')}, ${date.format('D MMMM YYYY')}` } @@ -829,7 +995,31 @@ const TimetableContainer = () => { Timetable
- + { spinning={loading} indicator={} > + { !showRegularTimetable ? ( + <> + {EXAM_LABEL} + + + + +
+ + + + + {EXAM_TIME_SLOTS.map((timeSlot) => ( + + {timeSlot.label} + + ))} + + + + + {allDates.map(({ display, weekday }) => ( + + +
+ {display} +
+ +
+ {weekday} +
+
+ + {EXAM_TIME_SLOTS.map((timeSlot) => { + const coursesInSlot = + organized[display]?.[timeSlot.slot] || [] + + return ( + + {coursesInSlot.map((code) => ( + + {code} + + ))} + + ) + })} + + ))} + +
+
+
+ +):( + <> {currentView === 'Day' && ( { dayDateString={dayDateString} /> )} + +)}