feat: match reference timetable grid matrix UI, support period cell arrays, preserve empty cells with dash without fake Free Period fallback
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
|||||||
Sparkles, KeyRound, ArrowRight, LogOut,
|
Sparkles, KeyRound, ArrowRight, LogOut,
|
||||||
AlertCircle, User, HelpCircle, Compass, ChevronRight, ExternalLink,
|
AlertCircle, User, HelpCircle, Compass, ChevronRight, ExternalLink,
|
||||||
Award, CheckCircle2, AlertTriangle, FileText, Calculator, TrendingUp,
|
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';
|
} from 'lucide-react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
@@ -35,7 +35,8 @@ export default function StudentDashboard() {
|
|||||||
|
|
||||||
// Navigation State
|
// Navigation State
|
||||||
const [activeTab, setActiveTab] = useState<'timetable' | 'attendance' | 'marks' | 'grades' | 'faculties'>('timetable');
|
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<number>(0);
|
const [selectedSemFilter, setSelectedSemFilter] = useState<number>(0);
|
||||||
|
|
||||||
// Loaded Data States
|
// Loaded Data States
|
||||||
@@ -67,13 +68,6 @@ export default function StudentDashboard() {
|
|||||||
}
|
}
|
||||||
}, [session?.token]);
|
}, [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) => {
|
const loadInitialData = async (token: string, force = false) => {
|
||||||
setIsInitialLoading(true);
|
setIsInitialLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
@@ -82,18 +76,6 @@ export default function StudentDashboard() {
|
|||||||
const p = await fetchImsProfile(token, reg, force);
|
const p = await fetchImsProfile(token, reg, force);
|
||||||
setProfile(p);
|
setProfile(p);
|
||||||
const tt = await fetchImsTimetable(token, p.registerNumber, force);
|
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) {
|
} catch (err: any) {
|
||||||
console.error('Error loading initial IMS data:', err);
|
console.error('Error loading initial IMS data:', err);
|
||||||
setError(err.message || 'Failed to load timetable.');
|
setError(err.message || 'Failed to load timetable.');
|
||||||
@@ -372,7 +354,6 @@ export default function StudentDashboard() {
|
|||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
// VIEW 2: AUTHENTICATED STUDENT DASHBOARD
|
// VIEW 2: AUTHENTICATED STUDENT DASHBOARD
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
const currentPeriods = (timetable && timetable[selectedDay]) || [];
|
|
||||||
const filteredGrades = gradesData?.results ? (
|
const filteredGrades = gradesData?.results ? (
|
||||||
selectedSemFilter === 0
|
selectedSemFilter === 0
|
||||||
? gradesData.results
|
? gradesData.results
|
||||||
@@ -731,108 +712,196 @@ export default function StudentDashboard() {
|
|||||||
{/* ─── 1. TIMETABLE TAB ─── */}
|
{/* ─── 1. TIMETABLE TAB ─── */}
|
||||||
{activeTab === 'timetable' && (
|
{activeTab === 'timetable' && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Day Switcher */}
|
{/* Header Title & Subtitle */}
|
||||||
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-white p-6 rounded-3xl border border-[#E5E7EB] shadow-sm">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-extrabold text-[#1E293B] tracking-tight">Class Time Table</h2>
|
||||||
|
<p className="text-xs font-semibold text-[#64748B] mt-1">
|
||||||
|
Weekly class schedule for Class : <span className="text-[#1E293B] font-bold">{profile?.department || 'B.Tech. AI & DS'} / {profile?.semester || 5} / E</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="px-3.5 py-1.5 rounded-full text-xs font-bold bg-emerald-50 text-emerald-700 border border-emerald-200 flex items-center gap-1.5">
|
||||||
|
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
||||||
|
CLASS Class : {profile?.department || 'B.Tech. AI & DS'} / {profile?.semester || 5} / E
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Toolbar: Day Filter Pills, Search Bar, Refresh */}
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3 bg-white p-4 rounded-2xl border border-[#E5E7EB] shadow-sm">
|
<div className="flex flex-wrap items-center justify-between gap-3 bg-white p-4 rounded-2xl border border-[#E5E7EB] shadow-sm">
|
||||||
<div className="flex items-center gap-2 overflow-x-auto py-1">
|
{/* Day Filter Pills */}
|
||||||
{(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] as const).map((dayKey) => {
|
<div className="flex items-center gap-1.5 overflow-x-auto py-1">
|
||||||
const count = timetable?.[dayKey]?.length || 0;
|
{[
|
||||||
const isSelected = selectedDay === dayKey;
|
{ 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 (
|
return (
|
||||||
<button
|
<button
|
||||||
key={dayKey}
|
key={day.key}
|
||||||
onClick={() => setSelectedDay(dayKey)}
|
onClick={() => setSelectedDayFilter(day.key as any)}
|
||||||
className={`flex items-center gap-2 px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all cursor-pointer capitalize ${
|
className={`flex items-center gap-1.5 px-3.5 py-1.5 rounded-xl text-xs font-bold transition-all cursor-pointer ${
|
||||||
isSelected
|
isSelected
|
||||||
? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] text-white shadow-md shadow-orange-500/20'
|
? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] text-white shadow-md shadow-orange-500/20'
|
||||||
: 'bg-slate-100 hover:bg-slate-200 text-[#64748B]'
|
: 'bg-slate-100 hover:bg-slate-200 text-[#64748B]'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>{dayKey}</span>
|
<span>{day.label}</span>
|
||||||
<span className={`px-1.5 py-0.2 rounded-md text-[10px] font-mono ${isSelected ? 'bg-white/20 text-white' : 'bg-white text-[#64748B] border border-[#E5E7EB]'}`}>
|
{isToday && (
|
||||||
{count}
|
<span className={`w-1.5 h-1.5 rounded-full ${isSelected ? 'bg-white' : 'bg-[#F97316]'}`} />
|
||||||
</span>
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className="text-xs text-emerald-600 font-bold flex items-center gap-1.5">
|
{/* Right Toolbar Controls */}
|
||||||
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
<div className="flex items-center gap-3 w-full sm:w-auto">
|
||||||
Live IMS Timetable
|
<div className="relative flex-1 sm:flex-initial">
|
||||||
</span>
|
<Search className="w-4 h-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={ttSearchQuery}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => session?.token && loadInitialData(session.token, true)}
|
||||||
|
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-xl text-xs font-bold bg-slate-100 hover:bg-slate-200 text-[#64748B] transition-all cursor-pointer"
|
||||||
|
>
|
||||||
|
<RotateCw className="w-3.5 h-3.5" />
|
||||||
|
<span>Refresh</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Timetable Grid Matrix */}
|
||||||
{isInitialLoading ? (
|
{isInitialLoading ? (
|
||||||
<div className="flex flex-col items-center justify-center py-20 gap-3 bg-white rounded-3xl border border-[#E5E7EB]">
|
<div className="flex flex-col items-center justify-center py-20 gap-3 bg-white rounded-3xl border border-[#E5E7EB]">
|
||||||
<div className="w-8 h-8 border-3 border-[#F97316] border-t-transparent rounded-full animate-spin" />
|
<div className="w-8 h-8 border-3 border-[#F97316] border-t-transparent rounded-full animate-spin" />
|
||||||
<p className="text-xs text-[#64748B]">Fetching timetable from RIT IMS...</p>
|
<p className="text-xs text-[#64748B]">Fetching timetable from RIT IMS...</p>
|
||||||
</div>
|
</div>
|
||||||
) : currentPeriods.length === 0 ? (
|
|
||||||
<div className="bg-white border border-[#E5E7EB] rounded-3xl p-12 text-center space-y-3">
|
|
||||||
<div className="w-12 h-12 rounded-2xl bg-orange-50 text-[#F97316] flex items-center justify-center mx-auto">
|
|
||||||
<Calendar className="w-6 h-6" />
|
|
||||||
</div>
|
|
||||||
<h3 className="text-base font-bold text-[#1E293B]">
|
|
||||||
No classes scheduled for {selectedDay.toUpperCase()}
|
|
||||||
</h3>
|
|
||||||
<p className="text-xs text-[#64748B] max-w-sm mx-auto">
|
|
||||||
Select another day of the week above to view your timetable periods and staff.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="overflow-x-auto rounded-3xl border border-[#E5E7EB] bg-white shadow-sm">
|
||||||
{currentPeriods.map((period) => (
|
<table className="w-full text-left border-collapse min-w-[900px]">
|
||||||
<motion.div
|
<thead>
|
||||||
key={period.periodNumber}
|
<tr className="bg-slate-50 border-b border-[#E5E7EB]">
|
||||||
initial={{ opacity: 0, y: 10 }}
|
<th className="py-4 px-4 text-center text-xs font-bold text-gray-500 uppercase tracking-wider w-36 border-r border-[#E5E7EB]">
|
||||||
animate={{ opacity: 1, y: 0 }}
|
Period
|
||||||
className="bg-white rounded-2xl p-5 border border-[#E5E7EB] hover:border-slate-300 shadow-sm transition-all"
|
</th>
|
||||||
>
|
{(['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'] as const)
|
||||||
{/* Header */}
|
.filter(d => selectedDayFilter === 'all' || selectedDayFilter === d)
|
||||||
<div className="flex items-center justify-between gap-2 mb-3">
|
.map((dayKey) => {
|
||||||
<div className="flex items-center gap-2">
|
const currentDayName = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'][new Date().getDay()];
|
||||||
<span className="w-6 h-6 rounded-lg bg-slate-100 text-[#1E293B] font-bold text-xs flex items-center justify-center">
|
const isToday = dayKey === currentDayName;
|
||||||
P{period.periodNumber}
|
return (
|
||||||
</span>
|
<th key={dayKey} className="py-4 px-4 text-center text-xs font-extrabold text-gray-800 capitalize border-r border-[#E5E7EB] last:border-r-0 min-w-[160px]">
|
||||||
<span className="text-xs font-semibold text-[#64748B] flex items-center gap-1">
|
<div className="flex items-center justify-center gap-1.5">
|
||||||
<Clock className="w-3 h-3 text-[#94A3B8]" />
|
<span>{dayKey}</span>
|
||||||
{period.timeSlot}
|
{isToday && (
|
||||||
</span>
|
<span className="px-1.5 py-0.5 rounded text-[9px] font-bold bg-emerald-500 text-white uppercase tracking-wider">
|
||||||
</div>
|
TODAY
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
<span
|
<tbody className="divide-y divide-[#E5E7EB]">
|
||||||
className={`px-2.5 py-0.5 rounded-full text-[10px] font-extrabold uppercase tracking-wider flex items-center gap-1 ${
|
{[1, 2, 3, 4, 5, 6, 7].map((pNum) => {
|
||||||
period.type === 'LAB'
|
const timeSlots = [
|
||||||
? 'bg-purple-100 text-purple-700 border border-purple-300'
|
'08:45 - 09:40',
|
||||||
: 'bg-blue-100 text-blue-700 border border-blue-300'
|
'09:40 - 10:35',
|
||||||
}`}
|
'10:50 - 11:45',
|
||||||
>
|
'11:45 - 12:40',
|
||||||
{period.type === 'LAB' ? <FlaskConical className="w-3 h-3 text-purple-600" /> : <BookOpen className="w-3 h-3 text-blue-600" />}
|
'01:30 - 02:25',
|
||||||
{period.type}
|
'02:25 - 03:20',
|
||||||
</span>
|
'03:20 - 04:15',
|
||||||
</div>
|
];
|
||||||
|
const slotTime = timeSlots[pNum - 1] || '';
|
||||||
|
|
||||||
{/* Subject Details */}
|
const activeDays = (['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'] as const)
|
||||||
<div className="space-y-1.5 mb-4">
|
.filter(d => selectedDayFilter === 'all' || selectedDayFilter === d);
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-[11px] font-mono font-bold text-[#F97316]">
|
|
||||||
{period.subjectCode}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<h3 className="text-base font-extrabold text-[#1E293B] line-clamp-1" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
|
|
||||||
{period.subjectName}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Faculty Footer */}
|
return (
|
||||||
<div className="pt-3 border-t border-[#E5E7EB] flex items-center justify-between text-xs text-[#475569]">
|
<tr key={pNum} className="hover:bg-slate-50/50 transition-colors">
|
||||||
<span className="truncate pr-2 font-medium">{period.staffName || 'Faculty Instructor'}</span>
|
{/* Period Column */}
|
||||||
{period.staffCode && (
|
<td className="py-4 px-3 text-center border-r border-[#E5E7EB] bg-slate-50/30 align-middle">
|
||||||
<span className="font-mono text-[10px] text-[#94A3B8]">{period.staffCode}</span>
|
<div className="font-extrabold text-xs text-emerald-600">Period {pNum}</div>
|
||||||
)}
|
<div className="text-[10px] font-medium text-gray-400 mt-0.5">{slotTime}</div>
|
||||||
</div>
|
</td>
|
||||||
</motion.div>
|
|
||||||
))}
|
{/* 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 (
|
||||||
|
<td key={dayKey} className="p-2.5 border-r border-[#E5E7EB] last:border-r-0 align-top min-w-[170px]">
|
||||||
|
{periods.length > 0 ? (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{periods.map((p, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="bg-[#F8FAFC] hover:bg-slate-100 p-3 rounded-2xl border border-[#E5E7EB] shadow-xs transition-all space-y-1"
|
||||||
|
>
|
||||||
|
<div className="font-bold text-xs text-gray-900 leading-tight">
|
||||||
|
{p.subjectName}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5 flex-wrap">
|
||||||
|
<span className={`inline-block text-[10px] font-bold px-1.5 py-0.5 rounded-md ${
|
||||||
|
p.type === 'LAB'
|
||||||
|
? 'bg-purple-50 text-purple-700 border border-purple-200'
|
||||||
|
: 'bg-emerald-50 text-emerald-700 border border-emerald-200'
|
||||||
|
}`}>
|
||||||
|
{p.subjectCode || p.type}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{p.staffName && (
|
||||||
|
<div className="text-[11px] font-medium text-gray-500 truncate">
|
||||||
|
{p.staffName}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center h-full min-h-[64px] text-gray-300 font-bold text-sm select-none">
|
||||||
|
—
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,12 +28,15 @@ export interface TimetablePeriod {
|
|||||||
type: 'THEORY' | 'LAB';
|
type: 'THEORY' | 'LAB';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DaySchedule = Record<number, TimetablePeriod[]>;
|
||||||
|
|
||||||
export interface WeeklySchedule {
|
export interface WeeklySchedule {
|
||||||
monday: TimetablePeriod[];
|
monday: DaySchedule;
|
||||||
tuesday: TimetablePeriod[];
|
tuesday: DaySchedule;
|
||||||
wednesday: TimetablePeriod[];
|
wednesday: DaySchedule;
|
||||||
thursday: TimetablePeriod[];
|
thursday: DaySchedule;
|
||||||
friday: TimetablePeriod[];
|
friday: DaySchedule;
|
||||||
|
saturday: DaySchedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SubjectAttendance {
|
export interface SubjectAttendance {
|
||||||
@@ -235,8 +238,17 @@ export function parseDepartmentCode(rawDept: string = ''): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function generateCurriculumSchedule(deptCode: string, semester: number): WeeklySchedule {
|
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 {
|
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: 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: 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' },
|
{ 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: 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: 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' },
|
{ 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: 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: 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: 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: 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: 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: 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: createDay([
|
||||||
wednesday: [
|
|
||||||
{ 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: 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: 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' },
|
{ 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: 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: 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' },
|
{ 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: 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: 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' },
|
{ 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: 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: 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' },
|
{ 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: 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: 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' },
|
{ 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: 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: 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' },
|
{ 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'];
|
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 {
|
export function normalizeTimetableSchedule(scheduleRaw: any): WeeklySchedule {
|
||||||
|
const emptyDay = (): DaySchedule => ({ 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [] });
|
||||||
const weekly: WeeklySchedule = {
|
const weekly: WeeklySchedule = {
|
||||||
monday: [],
|
monday: emptyDay(),
|
||||||
tuesday: [],
|
tuesday: emptyDay(),
|
||||||
wednesday: [],
|
wednesday: emptyDay(),
|
||||||
thursday: [],
|
thursday: emptyDay(),
|
||||||
friday: [],
|
friday: emptyDay(),
|
||||||
|
saturday: emptyDay(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!scheduleRaw) return weekly;
|
if (!scheduleRaw) return weekly;
|
||||||
|
|
||||||
const dayAliases: Record<string, keyof WeeklySchedule> = {
|
const dayAliases: Record<string, keyof WeeklySchedule> = {
|
||||||
'1': 'monday',
|
'1': 'monday', 'mon': 'monday', 'monday': 'monday',
|
||||||
'mon': 'monday',
|
'2': 'tuesday', 'tue': 'tuesday', 'tuesday': 'tuesday',
|
||||||
'monday': 'monday',
|
'3': 'wednesday', 'wed': 'wednesday', 'wednesday': 'wednesday',
|
||||||
'day 1': 'monday',
|
'4': 'thursday', 'thu': 'thursday', 'thursday': 'thursday',
|
||||||
'day1': 'monday',
|
'5': 'friday', 'fri': 'friday', 'friday': 'friday',
|
||||||
|
'6': 'saturday', 'sat': 'saturday', 'saturday': 'saturday',
|
||||||
'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',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const scheduleMap = scheduleRaw.schedule || scheduleRaw.data?.schedule || scheduleRaw.data || scheduleRaw;
|
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)) {
|
if (typeof dayObj === 'object' && !Array.isArray(dayObj)) {
|
||||||
Object.keys(dayObj).forEach((rawPeriod) => {
|
Object.keys(dayObj).forEach((rawPeriod) => {
|
||||||
const pNum = parseInt(rawPeriod, 10);
|
const pNum = parseInt(rawPeriod, 10);
|
||||||
|
if (pNum < 1 || pNum > 7) return;
|
||||||
const rawItems = dayObj[rawPeriod];
|
const rawItems = dayObj[rawPeriod];
|
||||||
const items = Array.isArray(rawItems) ? rawItems : [rawItems];
|
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');
|
const isLab = (subName && subName.toLowerCase().includes('lab')) || subCode.includes('71') || subCode.includes('81');
|
||||||
|
|
||||||
if (subName || subCode || staff) {
|
if (subName || subCode || staff) {
|
||||||
weekly[normalizedDay].push({
|
if (!weekly[normalizedDay][pNum]) weekly[normalizedDay][pNum] = [];
|
||||||
periodNumber: pNum || (weekly[normalizedDay].length + 1),
|
weekly[normalizedDay][pNum].push({
|
||||||
timeSlot: timeSlots[(pNum || (weekly[normalizedDay].length + 1)) - 1] || `Period ${pNum || 1}`,
|
periodNumber: pNum,
|
||||||
startTime: startTimes[(pNum || 1) - 1] || '08:45',
|
timeSlot: timeSlots[pNum - 1] || `Period ${pNum}`,
|
||||||
endTime: endTimes[(pNum || 1) - 1] || '09:40',
|
startTime: startTimes[pNum - 1] || '08:45',
|
||||||
|
endTime: endTimes[pNum - 1] || '09:40',
|
||||||
subjectCode: subCode || 'SUB',
|
subjectCode: subCode || 'SUB',
|
||||||
subjectName: subName || 'Core Subject',
|
subjectName: subName || 'Core Subject',
|
||||||
staffName: staff || 'Faculty Member',
|
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
|
* Replicates reference site's fetchTimetable() from imsScraper.ts
|
||||||
*/
|
*/
|
||||||
export async function fetchImsTimetable(_token: string, regNumber: string, _forceRefresh = false): Promise<WeeklySchedule> {
|
export async function fetchImsTimetable(_token: string, regNumber: string, _forceRefresh = false): Promise<WeeklySchedule> {
|
||||||
|
const emptyDay = (): DaySchedule => ({ 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [] });
|
||||||
let weeklySchedule: WeeklySchedule = {
|
let weeklySchedule: WeeklySchedule = {
|
||||||
monday: [], tuesday: [], wednesday: [], thursday: [], friday: [],
|
monday: emptyDay(), tuesday: emptyDay(), wednesday: emptyDay(), thursday: emptyDay(), friday: emptyDay(), saturday: emptyDay(),
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const session = getStoredImsSession();
|
const session = getStoredImsSession();
|
||||||
let targetUrl = session?.profile?.timetableUrl || '';
|
let targetUrl = session?.profile?.timetableUrl || '';
|
||||||
|
|
||||||
// If targetUrl is not stored yet, discover it dynamically from the report menu page
|
|
||||||
if (!targetUrl) {
|
if (!targetUrl) {
|
||||||
const navRes = await fetch('/ims/admin/grade/student/mark/report', { credentials: 'same-origin' });
|
const navRes = await fetch('/ims/admin/grade/student/mark/report', { credentials: 'same-origin' });
|
||||||
if (navRes.ok) {
|
if (navRes.ok) {
|
||||||
const navHtml = await navRes.text();
|
const html = await navRes.text();
|
||||||
const navDoc = new DOMParser().parseFromString(navHtml, 'text/html');
|
const doc = new DOMParser().parseFromString(html, 'text/html');
|
||||||
const ttLink = navDoc.querySelector('a[href*="student-time-table"]')?.getAttribute('href');
|
const ttLinkEl = doc.querySelector('a[href*="time-table"]');
|
||||||
if (ttLink) {
|
if (ttLinkEl) {
|
||||||
|
const ttLink = ttLinkEl.getAttribute('href') || '';
|
||||||
if (ttLink.startsWith('https://ims.ritchennai.edu.in')) {
|
if (ttLink.startsWith('https://ims.ritchennai.edu.in')) {
|
||||||
targetUrl = ttLink.replace('https://ims.ritchennai.edu.in', '/ims');
|
targetUrl = ttLink.replace('https://ims.ritchennai.edu.in', '/ims');
|
||||||
} else if (ttLink.startsWith('/')) {
|
} else if (ttLink.startsWith('/')) {
|
||||||
@@ -714,16 +691,15 @@ export async function fetchImsTimetable(_token: string, regNumber: string, _forc
|
|||||||
const html = await response.text();
|
const html = await response.text();
|
||||||
const doc = new DOMParser().parseFromString(html, 'text/html');
|
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'));
|
const forms = Array.from(doc.querySelectorAll('.period_form'));
|
||||||
if (forms.length > 0) {
|
if (forms.length > 0) {
|
||||||
const dayAliases: Record<string, keyof WeeklySchedule> = {
|
const dayAliases: Record<string, keyof WeeklySchedule> = {
|
||||||
'monday': 'monday', 'tuesday': 'tuesday', 'wednesday': 'wednesday',
|
'monday': 'monday', 'mon': 'monday', '1': 'monday',
|
||||||
'thursday': 'thursday', 'friday': 'friday',
|
'tuesday': 'tuesday', 'tue': 'tuesday', '2': 'tuesday',
|
||||||
};
|
'wednesday': 'wednesday', 'wed': 'wednesday', '3': 'wednesday',
|
||||||
|
'thursday': 'thursday', 'thu': 'thursday', '4': 'thursday',
|
||||||
const periodMap: Record<string, Record<number, TimetablePeriod>> = {
|
'friday': 'friday', 'fri': 'friday', '5': 'friday',
|
||||||
monday: {}, tuesday: {}, wednesday: {}, thursday: {}, friday: {}
|
'saturday': 'saturday', 'sat': 'saturday', '6': 'saturday',
|
||||||
};
|
};
|
||||||
|
|
||||||
forms.forEach(form => {
|
forms.forEach(form => {
|
||||||
@@ -732,7 +708,7 @@ export async function fetchImsTimetable(_token: string, regNumber: string, _forc
|
|||||||
|
|
||||||
const day = (dayInput?.value || '').toLowerCase().trim();
|
const day = (dayInput?.value || '').toLowerCase().trim();
|
||||||
const period = parseInt(periodInput?.value || '0', 10);
|
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];
|
const normalizedDay = dayAliases[day];
|
||||||
if (!normalizedDay) return;
|
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) {
|
if (!rawSubject) {
|
||||||
const fullTxt = form.textContent?.trim() || '';
|
const fullTxt = form.textContent?.trim() || '';
|
||||||
if (fullTxt) rawSubject = fullTxt.replace(/\s+/g, ' ').slice(0, 60);
|
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()})`;
|
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 rawSubLower = subjectName.toLowerCase();
|
||||||
const isLab = rawSubLower.includes('lab') || rawSubLower.includes('laboratory');
|
const isLab = rawSubLower.includes('lab') || rawSubLower.includes('laboratory');
|
||||||
|
|
||||||
const periodIndex = period - 1;
|
const periodItem: TimetablePeriod = {
|
||||||
const existing = periodMap[normalizedDay][period];
|
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) {
|
const existingList = weeklySchedule[normalizedDay][period] || [];
|
||||||
periodMap[normalizedDay][period] = {
|
const isDuplicate = existingList.some(
|
||||||
periodNumber: period,
|
p => p.subjectCode === subjectCode && p.staffName === staffName && p.subjectName === subjectName
|
||||||
timeSlot: periodIndex < timeSlots.length ? timeSlots[periodIndex] : `Period ${period}`,
|
);
|
||||||
startTime: periodIndex < startTimes.length ? startTimes[periodIndex] : '',
|
|
||||||
endTime: periodIndex < endTimes.length ? endTimes[periodIndex] : '',
|
if (!isDuplicate) {
|
||||||
subjectCode,
|
existingList.push(periodItem);
|
||||||
subjectName,
|
weeklySchedule[normalizedDay][period] = existingList;
|
||||||
staffName,
|
|
||||||
type: isLab ? 'LAB' : 'THEORY',
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// Merge staff names if same course or elective slot
|
|
||||||
if (staffName && !existing.staffName.includes(staffName)) {
|
|
||||||
existing.staffName += `, ${staffName}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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) {
|
} catch (err) {
|
||||||
console.warn('Direct timetable scrape failed:', err);
|
console.warn('Direct timetable scrape failed:', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to hardcoded schedule only if scraping returned nothing
|
// 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) {
|
if (totalPeriods === 0) {
|
||||||
const deduced = deduceStudentDetails(regNumber || '0000000000000');
|
const deduced = deduceStudentDetails(regNumber || '0000000000000');
|
||||||
weeklySchedule = generateCurriculumSchedule(deduced.departmentCode, deduced.semester);
|
weeklySchedule = generateCurriculumSchedule(deduced.departmentCode, deduced.semester);
|
||||||
|
|||||||
Reference in New Issue
Block a user