diff --git a/src/components/Timetable/ExamTimetable.jsx b/src/components/Timetable/ExamTimetable.jsx
index 05a9bfb..1cf58da 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'
@@ -41,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 ',
@@ -222,10 +224,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 = []
@@ -325,7 +328,8 @@ const Table = ({ timetable }) => {
@@ -487,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({})
}
@@ -557,7 +558,9 @@ const CourseFinderFilterForm = ({ setCoursesAndSlots }) => {
return null
}
-const PopupExample = () => {
+const PopupExample = ({
+ setShowRegularTimetable,
+}) => {
const [isOpen, setIsOpen] = useState(false)
const [timetable, setTimetable] = useState({})
const [courses, setCourses] = useState([])
@@ -653,7 +656,14 @@ const PopupExample = () => {
return (
-
+ {
+ if (isExamPeriod()) {
+ togglePopup()
+ }
+ }}
+>
End Sem
@@ -734,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 3b124df..2ac3dcd 100644
--- a/src/components/Timetable/TimetableContainer.jsx
+++ b/src/components/Timetable/TimetableContainer.jsx
@@ -37,26 +37,39 @@ 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 {
+ 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 TimetableContainer = () => {
+
const dispatch = useDispatch()
const semesterList = useSelector(selectSemesters)
const courseAPILoading = useSelector(selectCourseAPILoading)
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()
@@ -76,6 +89,7 @@ const TimetableContainer = () => {
const [dropdownVisible, setDropdownVisible] = useState(false)
const [coursesModalVisible, setCoursesModalVisible] = useState(false)
+
// Eplanner events state
const [eplannerEvents, setEplannerEvents] = useState({
personal: [],
@@ -196,7 +210,8 @@ const TimetableContainer = () => {
Personal
- handleDropdownItemClick('exam')}>
+ {
+ handleDropdownItemClick('exam')}}>
@@ -391,8 +406,7 @@ const TimetableContainer = () => {
const courseList = courseTimetableList.map((item) => item.course)
return courseList
}
-
- useEffect(() => {
+ useEffect(() => {
const fetchCourseData = async () => {
try {
setLoading(true)
@@ -445,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
@@ -702,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)
+}
+
+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
+}
+
+// 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'))
+ })
- addEplannerEvents()
+ const courseArr = filtered.map((item) => item.course)
- return events
+ 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')}`
}
@@ -818,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}
/>
)}
+ >
+)}