diff --git a/src/pages/StudentDashboard/StudentDashboard.tsx b/src/pages/StudentDashboard/StudentDashboard.tsx index 4bfe030..49374de 100644 --- a/src/pages/StudentDashboard/StudentDashboard.tsx +++ b/src/pages/StudentDashboard/StudentDashboard.tsx @@ -6,7 +6,8 @@ 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, + Star, Search, Filter, Grid, List, X } from 'lucide-react'; import { Link } from 'react-router-dom'; import { useAuth } from '@/context/AuthContext'; @@ -43,6 +44,85 @@ export default function StudentDashboard() { const [selectedSemFilter, setSelectedSemFilter] = useState(0); const [marksSubTab, setMarksSubTab] = useState<'cat' | 'assignment' | 'lab'>('cat'); + // Timetable Customization States (Matching Reference Agenda Layout) + const [ttViewMode, setTtViewMode] = useState<'grid' | 'daily'>('grid'); + const [ttFilterType, setTtFilterType] = useState<'ALL' | 'LAB' | 'THEORY' | 'ELECTIVE' | 'FREE'>('ALL'); + const [ttSearchQuery, setTtSearchQuery] = useState(''); + const [starredPeriods, setStarredPeriods] = useState>(() => { + try { + const saved = localStorage.getItem('rit_starred_periods'); + return saved ? JSON.parse(saved) : {}; + } catch { + return {}; + } + }); + + const [currentTimeMinutes, setCurrentTimeMinutes] = useState(() => { + const now = new Date(); + return now.getHours() * 60 + now.getMinutes(); + }); + + useEffect(() => { + const timer = setInterval(() => { + const now = new Date(); + setCurrentTimeMinutes(now.getHours() * 60 + now.getMinutes()); + }, 30000); + return () => clearInterval(timer); + }, []); + + const toggleStarPeriod = (key: string) => { + setStarredPeriods(prev => { + const next = { ...prev, [key]: !prev[key] }; + try { + localStorage.setItem('rit_starred_periods', JSON.stringify(next)); + } catch (err) { + console.warn('Failed to save starred period:', err); + } + return next; + }); + }; + + // Agenda Timetable Helpers & Indicators + const dayNamesMap: Record = { + 1: 'monday', 2: 'tuesday', 3: 'wednesday', 4: 'thursday', 5: 'friday' + }; + const todayKey = dayNamesMap[new Date().getDay()] || null; + + const periodTimeRanges: Record = { + 1: { start: 525, end: 580 }, + 2: { start: 580, end: 635 }, + 3: { start: 650, end: 705 }, + 4: { start: 705, end: 760 }, + 5: { start: 810, end: 865 }, + 6: { start: 865, end: 920 }, + 7: { start: 920, end: 975 }, + }; + + const isPeriodLive = (dayKey: string, pNum: number) => { + if (!todayKey || todayKey !== dayKey) return false; + const r = periodTimeRanges[pNum]; + if (!r) return false; + return currentTimeMinutes >= r.start && currentTimeMinutes < r.end; + }; + + const matchesFilter = (period: any) => { + if (!period) return false; + const query = ttSearchQuery.toLowerCase().trim(); + if (query) { + const matchSub = (period.subjectName || '').toLowerCase().includes(query); + const matchCode = (period.subjectCode || '').toLowerCase().includes(query); + const matchStaff = (period.staffName || '').toLowerCase().includes(query); + if (!matchSub && !matchCode && !matchStaff) return false; + } + + if (ttFilterType === 'LAB') return period.type === 'LAB'; + if (ttFilterType === 'THEORY') return period.type === 'THEORY' && period.subjectCode !== 'FREE'; + if (ttFilterType === 'ELECTIVE') return (period.subjectCode || '').includes('V1') || (period.subjectName || '').toLowerCase().includes('elective'); + if (ttFilterType === 'FREE') return period.subjectCode === 'FREE'; + + return true; + }; + // Loaded Data States const [profile, setProfile] = useState(session?.profile || null); const [timetable, setTimetable] = useState(null); @@ -741,38 +821,89 @@ export default function StudentDashboard() { })} - {/* ─── 1. TIMETABLE TAB ─── */} + {/* ─── 1. TIMETABLE TAB (NEXT-GEN AGENDA UI) ─── */} {activeTab === 'timetable' && (
- {/* Day Switcher */} -
-
- {(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] as const).map((dayKey) => { - const count = timetable?.[dayKey]?.length || 0; - const isSelected = selectedDay === dayKey; - return ( + {/* Top Toolbar: Filter Pills & Search & View Toggle */} +
+
+ + {/* Filter Pills (Matching Screenshot Top Bar) */} +
+ {[ + { id: 'ALL', label: 'ALL', color: 'bg-slate-900 text-white' }, + { id: 'LAB', label: 'LABORATORY', color: 'bg-amber-500 text-white shadow-amber-500/20' }, + { id: 'THEORY', label: 'THEORY', color: 'bg-blue-600 text-white shadow-blue-500/20' }, + { id: 'ELECTIVE', label: 'ELECTIVES', color: 'bg-emerald-600 text-white shadow-emerald-500/20' }, + { id: 'FREE', label: 'FREE PERIODS', color: 'bg-slate-500 text-white' }, + ].map((filter) => { + const isSelected = ttFilterType === filter.id; + return ( + + ); + })} +
+ + {/* Right Side: Search & View Switcher */} +
+ {/* Search Input */} +
+ + setTtSearchQuery(e.target.value)} + className="w-full pl-9 pr-4 py-2 rounded-2xl bg-slate-50 border border-[#E5E7EB] text-xs text-[#1E293B] focus:outline-none focus:border-[#F97316] transition-all" + /> + {ttSearchQuery && ( + + )} +
+ + {/* View Mode Switcher */} +
- ); - })} -
+ +
+
- - - Live IMS Timetable - +
{isInitialLoading ? ( @@ -780,72 +911,278 @@ export default function StudentDashboard() {

Fetching timetable from RIT IMS...

- ) : currentPeriods.length === 0 ? ( -
-
- + ) : ttViewMode === 'grid' ? ( + /* ─── WEEKLY MATRIX AGENDA VIEW (MATCHING USER SCREENSHOT) ─── */ +
+
+
+ + {/* Matrix Header Row: Days of Week */} +
+
TIME
+ {(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] as const).map((dayKey) => { + const isToday = todayKey === dayKey; + return ( +
+ {dayKey} + {isToday && } +
+ ); + })} +
+ + {/* Matrix Grid Periods (P1 to P7 with Gap Rows) */} +
+ + {[ + { type: 'period', pNum: 1, timeLabel: '08:45 AM' }, + { type: 'period', pNum: 2, timeLabel: '09:40 AM' }, + { type: 'gap', label: '15M TEA BREAK', timeLabel: '10:35 AM' }, + { type: 'period', pNum: 3, timeLabel: '10:50 AM' }, + { type: 'period', pNum: 4, timeLabel: '11:45 AM' }, + { type: 'gap', label: '50M LUNCH BREAK', timeLabel: '12:40 PM' }, + { type: 'period', pNum: 5, timeLabel: '01:30 PM' }, + { type: 'period', pNum: 6, timeLabel: '02:25 PM' }, + { type: 'period', pNum: 7, timeLabel: '03:20 PM' }, + ].map((rowItem, rIdx) => { + if (rowItem.type === 'gap') { + // Diagonal-Striped Gap Row (Matching Screenshot "1.5H GAP" block) + return ( +
+
+ {rowItem.timeLabel} +
+
+ + + {rowItem.label} + +
+
+ ); + } + + const pNum = rowItem.pNum!; + return ( +
+ {/* Time Column Marker */} +
+ {rowItem.timeLabel} + P{pNum} +
+ + {/* 5 Day Period Cells */} + {(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] as const).map((dayKey) => { + const dayList = timetable?.[dayKey] || []; + const period = dayList.find((p) => p.periodNumber === pNum); + + if (!period || !matchesFilter(period)) { + return ( +
+ - +
+ ); + } + + const starKey = `${dayKey}-${pNum}`; + const isStarred = starredPeriods[starKey]; + const isLiveNow = isPeriodLive(dayKey, pNum); + const isFree = period.subjectCode === 'FREE'; + const isLab = period.type === 'LAB'; + const isElective = (period.subjectCode || '').includes('V1') || (period.subjectName || '').toLowerCase().includes('elective'); + + return ( +
+ {/* Live Now Indicator Badge */} + {isLiveNow && ( + + + LIVE + + )} + +
+ {/* Subject Title */} +

+ {period.subjectName} +

+ + {/* Faculty Instructor */} + {!isFree && ( +

+ {period.staffName || 'Faculty Instructor'} +

+ )} +
+ + {/* Badges & Star Footer */} +
+
+ {period.subjectCode && !isFree && ( + + {period.subjectCode} + + )} + + {period.type} + +
+ + {/* Interactive Star Favorite Button (Matching Screenshot) */} + +
+
+ ); + })} +
+ ); + })} + +
+
-

- 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} - -
+ /* ─── DAILY CARD VIEW ─── */ +
+ {/* Day Switcher */} +
+
+ {(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] as const).map((dayKey) => { + const count = timetable?.[dayKey]?.length || 0; + const isSelected = selectedDay === dayKey; + return ( + + ); + })} +
+
- + {(timetable?.[selectedDay] || []).filter(matchesFilter).map((period) => { + const starKey = `${selectedDay}-${period.periodNumber}`; + const isStarred = starredPeriods[starKey]; + const isLiveNow = isPeriodLive(selectedDay, period.periodNumber); + + return ( + - {period.type === 'LAB' ? : } - {period.type} - -
+ {/* Header */} +
+
+ + P{period.periodNumber} + + + + {period.timeSlot} + +
- {/* Subject Details */} -
-
- - {period.subjectCode} - -
-

- {period.subjectName} -

-
+
+ {isLiveNow && ( + + LIVE + + )} + + {period.type === 'LAB' ? : } + {period.type} + +
+
- {/* Faculty Footer */} -
- {period.staffName || 'Faculty Instructor'} - {period.staffCode && ( - {period.staffCode} - )} -
- - ))} + {/* Subject Details */} +
+
+ + {period.subjectCode} + + +
+

+ {period.subjectName} +

+
+ + {/* Faculty Footer */} +
+ {period.staffName || 'Faculty Instructor'} + {period.staffCode && ( + {period.staffCode} + )} +
+ + ); + })} +
)}