diff --git a/src/pages/StudentDashboard/StudentDashboard.tsx b/src/pages/StudentDashboard/StudentDashboard.tsx index d646e90..eaae7e9 100644 --- a/src/pages/StudentDashboard/StudentDashboard.tsx +++ b/src/pages/StudentDashboard/StudentDashboard.tsx @@ -6,7 +6,7 @@ import { Sparkles, KeyRound, ArrowRight, LogOut, AlertCircle, User, HelpCircle, Compass, ChevronRight, ExternalLink, Award, CheckCircle2, AlertTriangle, FileText, Calculator, TrendingUp, - Layers, BookOpen, Check, Mail, Phone, RefreshCw, Edit2, Save, FlaskConical + Layers, BookOpen, Check, Mail, Phone, RefreshCw, Edit2, Save, FlaskConical, Search, RotateCw } from 'lucide-react'; import { Link } from 'react-router-dom'; import { useAuth } from '@/context/AuthContext'; @@ -35,7 +35,8 @@ export default function StudentDashboard() { // Navigation State const [activeTab, setActiveTab] = useState<'timetable' | 'attendance' | 'marks' | 'grades' | 'faculties'>('timetable'); - const [selectedDay, setSelectedDay] = useState<'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday'>('monday'); + const [selectedDayFilter, setSelectedDayFilter] = useState<'all' | 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday'>('all'); + const [ttSearchQuery, setTtSearchQuery] = useState(''); const [selectedSemFilter, setSelectedSemFilter] = useState(0); // Loaded Data States @@ -67,13 +68,6 @@ export default function StudentDashboard() { } }, [session?.token]); - // Set default day to today (Mon-Fri) - useEffect(() => { - const dayNames: (keyof WeeklySchedule)[] = ['monday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'friday']; - const today = dayNames[new Date().getDay()] || 'monday'; - setSelectedDay(today); - }, []); - const loadInitialData = async (token: string, force = false) => { setIsInitialLoading(true); setError(null); @@ -82,18 +76,6 @@ export default function StudentDashboard() { const p = await fetchImsProfile(token, reg, force); setProfile(p); const tt = await fetchImsTimetable(token, p.registerNumber, force); - setTimetable(tt); - - const dayNames: (keyof WeeklySchedule)[] = ['monday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'friday']; - const today = dayNames[new Date().getDay()] || 'monday'; - if (tt && tt[today] && tt[today].length > 0) { - setSelectedDay(today); - } else if (tt) { - const firstActive = (['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] as const).find( - d => tt[d] && tt[d].length > 0 - ); - if (firstActive) setSelectedDay(firstActive); - } } catch (err: any) { console.error('Error loading initial IMS data:', err); setError(err.message || 'Failed to load timetable.'); @@ -372,7 +354,6 @@ export default function StudentDashboard() { // ───────────────────────────────────────────────────────────── // VIEW 2: AUTHENTICATED STUDENT DASHBOARD // ───────────────────────────────────────────────────────────── - const currentPeriods = (timetable && timetable[selectedDay]) || []; const filteredGrades = gradesData?.results ? ( selectedSemFilter === 0 ? gradesData.results @@ -731,108 +712,196 @@ export default function StudentDashboard() { {/* ─── 1. TIMETABLE TAB ─── */} {activeTab === 'timetable' && (
- {/* Day Switcher */} + {/* Header Title & Subtitle */} +
+
+

Class Time Table

+

+ Weekly class schedule for Class : {profile?.department || 'B.Tech. AI & DS'} / {profile?.semester || 5} / E +

+
+ +
+ + + CLASS Class : {profile?.department || 'B.Tech. AI & DS'} / {profile?.semester || 5} / E + +
+
+ + {/* Toolbar: Day Filter Pills, Search Bar, Refresh */}
-
- {(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] as const).map((dayKey) => { - const count = timetable?.[dayKey]?.length || 0; - const isSelected = selectedDay === dayKey; + {/* Day Filter Pills */} +
+ {[ + { key: 'all', label: 'All Days' }, + { key: 'monday', label: 'Mon' }, + { key: 'tuesday', label: 'Tue' }, + { key: 'wednesday', label: 'Wed' }, + { key: 'thursday', label: 'Thu' }, + { key: 'friday', label: 'Fri' }, + { key: 'saturday', label: 'Sat' }, + ].map((day) => { + const isSelected = selectedDayFilter === day.key; + const currentDayName = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'][new Date().getDay()]; + const isToday = day.key === currentDayName; return ( ); })}
- - - Live IMS Timetable - + {/* Right Toolbar Controls */} +
+
+ + setTtSearchQuery(e.target.value)} + placeholder="Search subject or faculty" + className="w-full sm:w-64 pl-9 pr-3 py-1.5 rounded-xl text-xs bg-slate-50 border border-[#E5E7EB] focus:outline-none focus:border-[#F97316] text-gray-800 placeholder-gray-400 font-medium" + /> +
+ + +
+ {/* Timetable Grid Matrix */} {isInitialLoading ? (

Fetching timetable from RIT IMS...

- ) : currentPeriods.length === 0 ? ( -
-
- -
-

- No classes scheduled for {selectedDay.toUpperCase()} -

-

- Select another day of the week above to view your timetable periods and staff. -

-
) : ( -
- {currentPeriods.map((period) => ( - - {/* Header */} -
-
- - P{period.periodNumber} - - - - {period.timeSlot} - -
+
+ + + + + {(['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'] as const) + .filter(d => selectedDayFilter === 'all' || selectedDayFilter === d) + .map((dayKey) => { + const currentDayName = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'][new Date().getDay()]; + const isToday = dayKey === currentDayName; + return ( + + ); + })} + + - - {period.type === 'LAB' ? : } - {period.type} - - + + {[1, 2, 3, 4, 5, 6, 7].map((pNum) => { + const timeSlots = [ + '08:45 - 09:40', + '09:40 - 10:35', + '10:50 - 11:45', + '11:45 - 12:40', + '01:30 - 02:25', + '02:25 - 03:20', + '03:20 - 04:15', + ]; + const slotTime = timeSlots[pNum - 1] || ''; - {/* Subject Details */} -
-
- - {period.subjectCode} - -
-

- {period.subjectName} -

-
+ const activeDays = (['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'] as const) + .filter(d => selectedDayFilter === 'all' || selectedDayFilter === d); - {/* Faculty Footer */} -
- {period.staffName || 'Faculty Instructor'} - {period.staffCode && ( - {period.staffCode} - )} -
- - ))} + return ( + + {/* Period Column */} + + + {/* Days Columns */} + {activeDays.map((dayKey) => { + const daySched = timetable?.[dayKey]; + const rawPeriods = daySched?.[pNum] || []; + const periods = rawPeriods.filter((p) => { + if (!ttSearchQuery) return true; + const q = ttSearchQuery.toLowerCase(); + return ( + p.subjectName.toLowerCase().includes(q) || + p.subjectCode.toLowerCase().includes(q) || + p.staffName.toLowerCase().includes(q) + ); + }); + + return ( + + ); + })} + + ); + })} + +
+ Period + +
+ {dayKey} + {isToday && ( + + TODAY + + )} +
+
+
Period {pNum}
+
{slotTime}
+
+ {periods.length > 0 ? ( +
+ {periods.map((p, idx) => ( +
+
+ {p.subjectName} +
+
+ + {p.subjectCode || p.type} + +
+ {p.staffName && ( +
+ {p.staffName} +
+ )} +
+ ))} +
+ ) : ( +
+ — +
+ )} +
)}
diff --git a/src/services/imsService.ts b/src/services/imsService.ts index 029102a..c92d2b8 100644 --- a/src/services/imsService.ts +++ b/src/services/imsService.ts @@ -28,12 +28,15 @@ export interface TimetablePeriod { type: 'THEORY' | 'LAB'; } +export type DaySchedule = Record; + export interface WeeklySchedule { - monday: TimetablePeriod[]; - tuesday: TimetablePeriod[]; - wednesday: TimetablePeriod[]; - thursday: TimetablePeriod[]; - friday: TimetablePeriod[]; + monday: DaySchedule; + tuesday: DaySchedule; + wednesday: DaySchedule; + thursday: DaySchedule; + friday: DaySchedule; + saturday: DaySchedule; } export interface SubjectAttendance { @@ -235,8 +238,17 @@ export function parseDepartmentCode(rawDept: string = ''): string { } export function generateCurriculumSchedule(deptCode: string, semester: number): WeeklySchedule { + const createDay = (list: TimetablePeriod[]): DaySchedule => { + const day: DaySchedule = { 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [] }; + list.forEach(p => { + if (!day[p.periodNumber]) day[p.periodNumber] = []; + day[p.periodNumber].push(p); + }); + return day; + }; + return { - monday: [ + monday: createDay([ { periodNumber: 1, timeSlot: '08:45 AM - 09:40 AM', startTime: '08:45', endTime: '09:40', subjectCode: 'AD23511', subjectName: 'Deep Learning', staffName: 'ARTHI A. (CS87)', type: 'THEORY' }, { periodNumber: 2, timeSlot: '09:40 AM - 10:35 AM', startTime: '09:40', endTime: '10:35', subjectCode: 'AD23V12', subjectName: 'Big Data Analytics', staffName: 'KAVITHA V (CS253)', type: 'THEORY' }, { periodNumber: 3, timeSlot: '10:50 AM - 11:45 AM', startTime: '10:50', endTime: '11:45', subjectCode: 'CS23511', subjectName: 'Computer Networks', staffName: 'SELVAKUMARAN S. (CS91)', type: 'THEORY' }, @@ -244,17 +256,16 @@ export function generateCurriculumSchedule(deptCode: string, semester: number): { periodNumber: 5, timeSlot: '01:30 PM - 02:25 PM', startTime: '13:30', endTime: '14:25', subjectCode: 'CB23531', subjectName: 'Business Analytics', staffName: 'FOUZIA SULTHANA K. (CS84)', type: 'THEORY' }, { periodNumber: 6, timeSlot: '02:25 PM - 03:20 PM', startTime: '14:25', endTime: '15:20', subjectCode: 'AD23532', subjectName: 'Data Exploration & Visualization', staffName: 'DR.VIDHYA M (CS256)', type: 'THEORY' }, { periodNumber: 7, timeSlot: '03:20 PM - 04:15 PM', startTime: '15:20', endTime: '16:15', subjectCode: 'MENT', subjectName: 'Mentoring & Library', staffName: 'ARTHI A. (CS87)', type: 'THEORY' }, - ], - tuesday: [ + ]), + tuesday: createDay([ { periodNumber: 1, timeSlot: '08:45 AM - 09:40 AM', startTime: '08:45', endTime: '09:40', subjectCode: 'AD23V15', subjectName: 'Image and Video Analytics', staffName: 'JOEL J (CS226)', type: 'THEORY' }, { periodNumber: 2, timeSlot: '09:40 AM - 10:35 AM', startTime: '09:40', endTime: '10:35', subjectCode: 'AD23532', subjectName: 'Data Exploration & Visualization', staffName: 'DR.VIDHYA M (CS256)', type: 'THEORY' }, { periodNumber: 3, timeSlot: '10:50 AM - 11:45 AM', startTime: '10:50', endTime: '11:45', subjectCode: 'AD23511', subjectName: 'Deep Learning', staffName: 'ARTHI A. (CS87)', type: 'THEORY' }, { periodNumber: 4, timeSlot: '11:45 AM - 12:40 PM', startTime: '11:45', endTime: '12:40', subjectCode: 'CS23521', subjectName: 'Computer Networks Laboratory', staffName: 'SELVAKUMARAN S. (CS91)', type: 'LAB' }, { periodNumber: 5, timeSlot: '01:30 PM - 02:25 PM', startTime: '13:30', endTime: '14:25', subjectCode: 'CS23521', subjectName: 'Computer Networks Laboratory', staffName: 'SELVAKUMARAN S. (CS91)', type: 'LAB' }, { periodNumber: 6, timeSlot: '02:25 PM - 03:20 PM', startTime: '14:25', endTime: '15:20', subjectCode: 'CB23531', subjectName: 'Business Analytics', staffName: 'FOUZIA SULTHANA K. (CS84)', type: 'THEORY' }, - { periodNumber: 7, timeSlot: '03:20 PM - 04:15 PM', startTime: '15:20', endTime: '16:15', subjectCode: 'MENT', subjectName: 'Mentoring & Guidance', staffName: 'FOUZIA SULTHANA K. (CS84)', type: 'THEORY' }, - ], - wednesday: [ + ]), + wednesday: createDay([ { periodNumber: 1, timeSlot: '08:45 AM - 09:40 AM', startTime: '08:45', endTime: '09:40', subjectCode: 'AD23521', subjectName: 'Deep Learning Laboratory', staffName: 'ARTHI A. (CS87)', type: 'LAB' }, { periodNumber: 2, timeSlot: '09:40 AM - 10:35 AM', startTime: '09:40', endTime: '10:35', subjectCode: 'AD23521', subjectName: 'Deep Learning Laboratory', staffName: 'ARTHI A. (CS87)', type: 'LAB' }, { periodNumber: 3, timeSlot: '10:50 AM - 11:45 AM', startTime: '10:50', endTime: '11:45', subjectCode: 'AD23V15', subjectName: 'Image and Video Analytics', staffName: 'JOEL J (CS226)', type: 'THEORY' }, @@ -262,8 +273,8 @@ export function generateCurriculumSchedule(deptCode: string, semester: number): { periodNumber: 5, timeSlot: '01:30 PM - 02:25 PM', startTime: '13:30', endTime: '14:25', subjectCode: 'AD23V12', subjectName: 'Big Data Analytics', staffName: 'KAVITHA V (CS253)', type: 'THEORY' }, { periodNumber: 6, timeSlot: '02:25 PM - 03:20 PM', startTime: '14:25', endTime: '15:20', subjectCode: 'CS23511', subjectName: 'Computer Networks', staffName: 'SELVAKUMARAN S. (CS91)', type: 'THEORY' }, { periodNumber: 7, timeSlot: '03:20 PM - 04:15 PM', startTime: '15:20', endTime: '16:15', subjectCode: 'MENT', subjectName: 'Mentoring Session', staffName: 'KAVITHA V (CS253)', type: 'THEORY' }, - ], - thursday: [ + ]), + thursday: createDay([ { periodNumber: 1, timeSlot: '08:45 AM - 09:40 AM', startTime: '08:45', endTime: '09:40', subjectCode: 'CB23531', subjectName: 'Business Analytics', staffName: 'FOUZIA SULTHANA K. (CS84)', type: 'THEORY' }, { periodNumber: 2, timeSlot: '09:40 AM - 10:35 AM', startTime: '09:40', endTime: '10:35', subjectCode: 'CS23511', subjectName: 'Computer Networks', staffName: 'SELVAKUMARAN S. (CS91)', type: 'THEORY' }, { periodNumber: 3, timeSlot: '10:50 AM - 11:45 AM', startTime: '10:50', endTime: '11:45', subjectCode: 'AD23511', subjectName: 'Deep Learning', staffName: 'ARTHI A. (CS87)', type: 'THEORY' }, @@ -271,8 +282,8 @@ export function generateCurriculumSchedule(deptCode: string, semester: number): { periodNumber: 5, timeSlot: '01:30 PM - 02:25 PM', startTime: '13:30', endTime: '14:25', subjectCode: 'AD23V12', subjectName: 'Big Data Analytics', staffName: 'KAVITHA V (CS253)', type: 'THEORY' }, { periodNumber: 6, timeSlot: '02:25 PM - 03:20 PM', startTime: '14:25', endTime: '15:20', subjectCode: 'AD23532', subjectName: 'Data Exploration & Visualization', staffName: 'DR.VIDHYA M (CS256)', type: 'THEORY' }, { periodNumber: 7, timeSlot: '03:20 PM - 04:15 PM', startTime: '15:20', endTime: '16:15', subjectCode: 'MENT', subjectName: 'Mentoring Hour', staffName: 'FOUZIA SULTHANA K. (CS84)', type: 'THEORY' }, - ], - friday: [ + ]), + friday: createDay([ { periodNumber: 1, timeSlot: '08:45 AM - 09:40 AM', startTime: '08:45', endTime: '09:40', subjectCode: 'AD23V12', subjectName: 'Big Data Analytics', staffName: 'KAVITHA V (CS253)', type: 'THEORY' }, { periodNumber: 2, timeSlot: '09:40 AM - 10:35 AM', startTime: '09:40', endTime: '10:35', subjectCode: 'AD23V15', subjectName: 'Image and Video Analytics', staffName: 'JOEL J (CS226)', type: 'THEORY' }, { periodNumber: 3, timeSlot: '10:50 AM - 11:45 AM', startTime: '10:50', endTime: '11:45', subjectCode: 'AD23511', subjectName: 'Deep Learning', staffName: 'ARTHI A. (CS87)', type: 'THEORY' }, @@ -280,7 +291,16 @@ export function generateCurriculumSchedule(deptCode: string, semester: number): { periodNumber: 5, timeSlot: '01:30 PM - 02:25 PM', startTime: '13:30', endTime: '14:25', subjectCode: 'AD23532', subjectName: 'Data Exploration & Visualization', staffName: 'DR.VIDHYA M (CS256)', type: 'THEORY' }, { periodNumber: 6, timeSlot: '02:25 PM - 03:20 PM', startTime: '14:25', endTime: '15:20', subjectCode: 'CB23531', subjectName: 'Business Analytics', staffName: 'FOUZIA SULTHANA K. (CS84)', type: 'THEORY' }, { periodNumber: 7, timeSlot: '03:20 PM - 04:15 PM', startTime: '15:20', endTime: '16:15', subjectCode: 'CB23531', subjectName: 'Business Analytics', staffName: 'FOUZIA SULTHANA K. (CS84)', type: 'THEORY' }, - ], + ]), + saturday: createDay([ + { periodNumber: 1, timeSlot: '08:45 AM - 09:40 AM', startTime: '08:45', endTime: '09:40', subjectCode: 'MX23511', subjectName: 'Disaster Risk Reduction and Management', staffName: 'DR. SRINIVASAN M P (PH22)', type: 'THEORY' }, + { periodNumber: 2, timeSlot: '09:40 AM - 10:35 AM', startTime: '09:40', endTime: '10:35', subjectCode: 'AD23IC3', subjectName: 'Tableau - Data Visualization', staffName: 'FOUZIA SULTHANA K. (CS84)', type: 'LAB' }, + { periodNumber: 3, timeSlot: '10:50 AM - 11:45 AM', startTime: '10:50', endTime: '11:45', subjectCode: 'MX23511', subjectName: 'Disaster Risk Reduction and Management', staffName: 'DR. SRINIVASAN M P (PH22)', type: 'THEORY' }, + { periodNumber: 4, timeSlot: '11:45 AM - 12:40 PM', startTime: '11:45', endTime: '12:40', subjectCode: 'MX23511', subjectName: 'Disaster Risk Reduction and Management', staffName: 'DR. SRINIVASAN M P (PH22)', type: 'THEORY' }, + { periodNumber: 5, timeSlot: '01:30 PM - 02:25 PM', startTime: '13:30', endTime: '14:25', subjectCode: 'MX23511', subjectName: 'Disaster Risk Reduction and Management', staffName: 'DR. SRINIVASAN M P (PH22)', type: 'THEORY' }, + { periodNumber: 6, timeSlot: '02:25 PM - 03:20 PM', startTime: '14:25', endTime: '15:20', subjectCode: 'MX23511', subjectName: 'Disaster Risk Reduction and Management', staffName: 'DR. SRINIVASAN M P (PH22)', type: 'THEORY' }, + { periodNumber: 7, timeSlot: '03:20 PM - 04:15 PM', startTime: '15:20', endTime: '16:15', subjectCode: 'AD23IC3', subjectName: 'Tableau - Data Visualization', staffName: 'FOUZIA SULTHANA K. (CS84)', type: 'LAB' }, + ]), }; } @@ -300,46 +320,25 @@ const startTimes = ['08:45', '09:40', '10:50', '11:45', '13:30', '14:25', '15:20 const endTimes = ['09:40', '10:35', '11:45', '12:40', '14:25', '15:20', '16:15', '17:10', '18:05', '19:00']; export function normalizeTimetableSchedule(scheduleRaw: any): WeeklySchedule { + const emptyDay = (): DaySchedule => ({ 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [] }); const weekly: WeeklySchedule = { - monday: [], - tuesday: [], - wednesday: [], - thursday: [], - friday: [], + monday: emptyDay(), + tuesday: emptyDay(), + wednesday: emptyDay(), + thursday: emptyDay(), + friday: emptyDay(), + saturday: emptyDay(), }; if (!scheduleRaw) return weekly; const dayAliases: Record = { - '1': 'monday', - 'mon': 'monday', - 'monday': 'monday', - 'day 1': 'monday', - 'day1': 'monday', - - '2': 'tuesday', - 'tue': 'tuesday', - 'tuesday': 'tuesday', - 'day 2': 'tuesday', - 'day2': 'tuesday', - - '3': 'wednesday', - 'wed': 'wednesday', - 'wednesday': 'wednesday', - 'day 3': 'wednesday', - 'day3': 'wednesday', - - '4': 'thursday', - 'thu': 'thursday', - 'thursday': 'thursday', - 'day 4': 'thursday', - 'day4': 'thursday', - - '5': 'friday', - 'fri': 'friday', - 'friday': 'friday', - 'day 5': 'friday', - 'day5': 'friday', + '1': 'monday', 'mon': 'monday', 'monday': 'monday', + '2': 'tuesday', 'tue': 'tuesday', 'tuesday': 'tuesday', + '3': 'wednesday', 'wed': 'wednesday', 'wednesday': 'wednesday', + '4': 'thursday', 'thu': 'thursday', 'thursday': 'thursday', + '5': 'friday', 'fri': 'friday', 'friday': 'friday', + '6': 'saturday', 'sat': 'saturday', 'saturday': 'saturday', }; const scheduleMap = scheduleRaw.schedule || scheduleRaw.data?.schedule || scheduleRaw.data || scheduleRaw; @@ -353,6 +352,7 @@ export function normalizeTimetableSchedule(scheduleRaw: any): WeeklySchedule { if (typeof dayObj === 'object' && !Array.isArray(dayObj)) { Object.keys(dayObj).forEach((rawPeriod) => { const pNum = parseInt(rawPeriod, 10); + if (pNum < 1 || pNum > 7) return; const rawItems = dayObj[rawPeriod]; const items = Array.isArray(rawItems) ? rawItems : [rawItems]; @@ -365,11 +365,12 @@ export function normalizeTimetableSchedule(scheduleRaw: any): WeeklySchedule { const isLab = (subName && subName.toLowerCase().includes('lab')) || subCode.includes('71') || subCode.includes('81'); if (subName || subCode || staff) { - weekly[normalizedDay].push({ - periodNumber: pNum || (weekly[normalizedDay].length + 1), - timeSlot: timeSlots[(pNum || (weekly[normalizedDay].length + 1)) - 1] || `Period ${pNum || 1}`, - startTime: startTimes[(pNum || 1) - 1] || '08:45', - endTime: endTimes[(pNum || 1) - 1] || '09:40', + if (!weekly[normalizedDay][pNum]) weekly[normalizedDay][pNum] = []; + weekly[normalizedDay][pNum].push({ + periodNumber: pNum, + timeSlot: timeSlots[pNum - 1] || `Period ${pNum}`, + startTime: startTimes[pNum - 1] || '08:45', + endTime: endTimes[pNum - 1] || '09:40', subjectCode: subCode || 'SUB', subjectName: subName || 'Core Subject', staffName: staff || 'Faculty Member', @@ -379,32 +380,7 @@ export function normalizeTimetableSchedule(scheduleRaw: any): WeeklySchedule { } }); }); - } else if (Array.isArray(dayObj)) { - dayObj.forEach((item: any, idx: number) => { - if (!item) return; - const pNum = item.period || item.periodNumber || (idx + 1); - const subName = item.subjectName || item.name || item.subject || ''; - const subCode = item.subjectCode || item.code || ''; - const staff = item.staffName || item.staff || ''; - const isLab = (subName && subName.toLowerCase().includes('lab')) || subCode.includes('71') || subCode.includes('81'); - - if (subName || subCode || staff) { - weekly[normalizedDay].push({ - periodNumber: pNum, - timeSlot: timeSlots[pNum - 1] || `Period ${pNum}`, - startTime: startTimes[pNum - 1] || '08:45', - endTime: endTimes[pNum - 1] || '09:40', - subjectCode: subCode || 'SUB', - subjectName: subName || 'Core Subject', - staffName: staff || 'Faculty Member', - staffCode: item.staffCode, - type: isLab ? 'LAB' : 'THEORY', - }); - } - }); } - - weekly[normalizedDay].sort((a, b) => a.periodNumber - b.periodNumber); }); } @@ -679,22 +655,23 @@ export function updateImsProfileName(newName: string): StudentProfile | null { * Replicates reference site's fetchTimetable() from imsScraper.ts */ export async function fetchImsTimetable(_token: string, regNumber: string, _forceRefresh = false): Promise { + const emptyDay = (): DaySchedule => ({ 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [] }); let weeklySchedule: WeeklySchedule = { - monday: [], tuesday: [], wednesday: [], thursday: [], friday: [], + monday: emptyDay(), tuesday: emptyDay(), wednesday: emptyDay(), thursday: emptyDay(), friday: emptyDay(), saturday: emptyDay(), }; try { const session = getStoredImsSession(); let targetUrl = session?.profile?.timetableUrl || ''; - // If targetUrl is not stored yet, discover it dynamically from the report menu page if (!targetUrl) { const navRes = await fetch('/ims/admin/grade/student/mark/report', { credentials: 'same-origin' }); if (navRes.ok) { - const navHtml = await navRes.text(); - const navDoc = new DOMParser().parseFromString(navHtml, 'text/html'); - const ttLink = navDoc.querySelector('a[href*="student-time-table"]')?.getAttribute('href'); - if (ttLink) { + const html = await navRes.text(); + const doc = new DOMParser().parseFromString(html, 'text/html'); + const ttLinkEl = doc.querySelector('a[href*="time-table"]'); + if (ttLinkEl) { + const ttLink = ttLinkEl.getAttribute('href') || ''; if (ttLink.startsWith('https://ims.ritchennai.edu.in')) { targetUrl = ttLink.replace('https://ims.ritchennai.edu.in', '/ims'); } else if (ttLink.startsWith('/')) { @@ -714,16 +691,15 @@ export async function fetchImsTimetable(_token: string, regNumber: string, _forc const html = await response.text(); const doc = new DOMParser().parseFromString(html, 'text/html'); - // Parse .period_form elements — exact same logic as reference site const forms = Array.from(doc.querySelectorAll('.period_form')); if (forms.length > 0) { const dayAliases: Record = { - 'monday': 'monday', 'tuesday': 'tuesday', 'wednesday': 'wednesday', - 'thursday': 'thursday', 'friday': 'friday', - }; - - const periodMap: Record> = { - monday: {}, tuesday: {}, wednesday: {}, thursday: {}, friday: {} + 'monday': 'monday', 'mon': 'monday', '1': 'monday', + 'tuesday': 'tuesday', 'tue': 'tuesday', '2': 'tuesday', + 'wednesday': 'wednesday', 'wed': 'wednesday', '3': 'wednesday', + 'thursday': 'thursday', 'thu': 'thursday', '4': 'thursday', + 'friday': 'friday', 'fri': 'friday', '5': 'friday', + 'saturday': 'saturday', 'sat': 'saturday', '6': 'saturday', }; forms.forEach(form => { @@ -732,7 +708,7 @@ export async function fetchImsTimetable(_token: string, regNumber: string, _forc const day = (dayInput?.value || '').toLowerCase().trim(); const period = parseInt(periodInput?.value || '0', 10); - if (!day || !period || isNaN(period) || period < 1 || period > 10) return; + if (!day || !period || isNaN(period) || period < 1 || period > 7) return; const normalizedDay = dayAliases[day]; if (!normalizedDay) return; @@ -753,7 +729,6 @@ export async function fetchImsTimetable(_token: string, regNumber: string, _forc }); } - // Fallback text check for non-standard subjects (NPTEL, Aptitude, Mentoring, Placement, Library, etc.) if (!rawSubject) { const fullTxt = form.textContent?.trim() || ''; if (fullTxt) rawSubject = fullTxt.replace(/\s+/g, ' ').slice(0, 60); @@ -777,66 +752,39 @@ export async function fetchImsTimetable(_token: string, regNumber: string, _forc staffName = `${staffMatch[1].trim()} (${staffMatch[2].trim()})`; } - // STRICT USER RULE: Mark as LAB ONLY if subject name contains 'lab' or 'laboratory' (case insensitive) const rawSubLower = subjectName.toLowerCase(); const isLab = rawSubLower.includes('lab') || rawSubLower.includes('laboratory'); - const periodIndex = period - 1; - const existing = periodMap[normalizedDay][period]; + const periodItem: TimetablePeriod = { + periodNumber: period, + timeSlot: (period - 1) < timeSlots.length ? timeSlots[period - 1] : `Period ${period}`, + startTime: (period - 1) < startTimes.length ? startTimes[period - 1] : '', + endTime: (period - 1) < endTimes.length ? endTimes[period - 1] : '', + subjectCode, + subjectName, + staffName, + type: isLab ? 'LAB' : 'THEORY', + }; - if (!existing) { - periodMap[normalizedDay][period] = { - periodNumber: period, - timeSlot: periodIndex < timeSlots.length ? timeSlots[periodIndex] : `Period ${period}`, - startTime: periodIndex < startTimes.length ? startTimes[periodIndex] : '', - endTime: periodIndex < endTimes.length ? endTimes[periodIndex] : '', - subjectCode, - subjectName, - staffName, - type: isLab ? 'LAB' : 'THEORY', - }; - } else { - // Merge staff names if same course or elective slot - if (staffName && !existing.staffName.includes(staffName)) { - existing.staffName += `, ${staffName}`; - } + const existingList = weeklySchedule[normalizedDay][period] || []; + const isDuplicate = existingList.some( + p => p.subjectCode === subjectCode && p.staffName === staffName && p.subjectName === subjectName + ); + + if (!isDuplicate) { + existingList.push(periodItem); + weeklySchedule[normalizedDay][period] = existingList; } }); - - // Populate weeklySchedule from periodMap (Ensure EVERY day Monday-Friday has EXACTLY 7 periods) - const standardDays: (keyof WeeklySchedule)[] = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday']; - - standardDays.forEach((day) => { - const existingMap = periodMap[day] || {}; - const fullDayPeriods: TimetablePeriod[] = []; - - for (let pNum = 1; pNum <= 7; pNum++) { - const periodIndex = pNum - 1; - if (existingMap[pNum]) { - fullDayPeriods.push(existingMap[pNum]); - } else { - fullDayPeriods.push({ - periodNumber: pNum, - timeSlot: timeSlots[periodIndex] || `Period ${pNum}`, - startTime: startTimes[periodIndex] || '', - endTime: endTimes[periodIndex] || '', - subjectCode: 'FREE', - subjectName: 'Free Period / Library', - staffName: 'Self Study', - type: 'THEORY', - }); - } - } - - weeklySchedule[day] = fullDayPeriods; - }); } } catch (err) { console.warn('Direct timetable scrape failed:', err); } // Fallback to hardcoded schedule only if scraping returned nothing - const totalPeriods = Object.values(weeklySchedule).reduce((acc, periods) => acc + periods.length, 0); + const totalPeriods = Object.values(weeklySchedule).reduce((acc: number, daySched: any) => { + return acc + Object.values(daySched || {}).reduce((dAcc: number, pList: any) => dAcc + (pList?.length || 0), 0); + }, 0); if (totalPeriods === 0) { const deduced = deduceStudentDetails(regNumber || '0000000000000'); weeklySchedule = generateCurriculumSchedule(deduced.departmentCode, deduced.semester);