425 lines
26 KiB
TypeScript
425 lines
26 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import api from '../store/auth';
|
|
import {
|
|
Plus, Calendar as CalendarIcon, Clock, MapPin,
|
|
ChevronRight, ChevronLeft, Search, X,
|
|
Download, Filter, Sparkles, School,
|
|
Users, BookOpen, Bell, Trash2, Edit2,
|
|
CheckCircle2, AlertCircle, Info, MoreHorizontal,
|
|
CalendarDays, Coffee, Video, UserCircle
|
|
} from 'lucide-react';
|
|
|
|
interface Event {
|
|
id: number;
|
|
title: string;
|
|
description?: string;
|
|
event_date: string;
|
|
event_type: 'general' | 'holiday' | 'exam' | 'meeting' | 'class';
|
|
start_time?: string;
|
|
end_time?: string;
|
|
location?: string;
|
|
status?: 'active' | 'upcoming' | 'completed';
|
|
}
|
|
|
|
export default function Events() {
|
|
const [events, setEvents] = useState<Event[]>([]);
|
|
const [showModal, setShowModal] = useState(false);
|
|
const [loading, setLoading] = useState(true);
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
|
|
|
|
const [form, setForm] = useState({
|
|
title: '',
|
|
description: '',
|
|
event_date: '',
|
|
event_type: 'general' as Event['event_type'],
|
|
start_time: '09:00',
|
|
end_time: '10:00',
|
|
location: ''
|
|
});
|
|
|
|
useEffect(() => {
|
|
loadEvents();
|
|
}, []);
|
|
|
|
const loadEvents = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const res = await api.get('/events');
|
|
setEvents(res.data.map((e: any) => ({
|
|
...e,
|
|
status: new Date(e.event_date) < new Date() ? 'completed' : 'upcoming',
|
|
start_time: e.start_time || '09:00',
|
|
end_time: e.end_time || '10:00',
|
|
location: e.location || 'Lecture Hall A'
|
|
})));
|
|
} catch (err) {
|
|
setEvents(mockEvents);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
try {
|
|
await api.post('/events', form);
|
|
setShowModal(false);
|
|
setForm({
|
|
title: '',
|
|
description: '',
|
|
event_date: '',
|
|
event_type: 'general',
|
|
start_time: '09:00',
|
|
end_time: '10:00',
|
|
location: ''
|
|
});
|
|
loadEvents();
|
|
} catch (err) {
|
|
alert('Failed to establish event record');
|
|
}
|
|
};
|
|
|
|
const deleteEvent = async (id: number) => {
|
|
if (!confirm('Cancel this scheduled event?')) return;
|
|
try {
|
|
await api.delete(`/events/${id}`);
|
|
loadEvents();
|
|
} catch (err) {
|
|
alert('Failed to remove record');
|
|
}
|
|
};
|
|
|
|
const filteredEvents = events.filter(e =>
|
|
e.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
e.description?.toLowerCase().includes(searchQuery.toLowerCase())
|
|
);
|
|
|
|
const stats = [
|
|
{ label: 'Weekly Periods', value: '28', icon: BookOpen, color: 'blue', trend: 'Full' },
|
|
{ label: 'Pending Meetings', value: events.filter(e => e.event_type === 'meeting').length, icon: Users, color: 'emerald', trend: 'Confirmed' },
|
|
{ label: 'System Holidays', value: '2', icon: Coffee, color: 'orange', trend: 'Upcoming' },
|
|
{ label: 'Exam Windows', value: events.filter(e => e.event_type === 'exam').length, icon: CalendarDays, color: 'purple', trend: 'Finals' },
|
|
];
|
|
|
|
const getTypeStyles = (type: Event['event_type']) => {
|
|
switch (type) {
|
|
case 'exam': return { bg: 'bg-rose-50 dark:bg-rose-950/30', text: 'text-rose-600 dark:text-rose-400', border: 'border-rose-100 dark:border-rose-900/30', dot: 'bg-rose-500', icon: AlertCircle };
|
|
case 'meeting': return { bg: 'bg-purple-50 dark:bg-purple-950/30', text: 'text-purple-600 dark:text-purple-400', border: 'border-purple-100 dark:border-purple-900/30', dot: 'bg-purple-500', icon: Video };
|
|
case 'class': return { bg: 'bg-blue-50 dark:bg-blue-900/20', text: 'text-blue-600 dark:text-blue-400', border: 'border-blue-100 dark:border-blue-900/30', dot: 'bg-blue-500', icon: BookOpen };
|
|
case 'holiday': return { bg: 'bg-emerald-50 dark:bg-emerald-950/30', text: 'text-emerald-600 dark:text-emerald-400', border: 'border-emerald-100 dark:border-emerald-900/30', dot: 'bg-emerald-500', icon: Coffee };
|
|
default: return { bg: 'bg-slate-50 dark:bg-slate-900/20', text: 'text-slate-500 dark:text-slate-400', border: 'border-slate-100 dark:border-slate-800/30', dot: 'bg-slate-500', icon: Info };
|
|
}
|
|
};
|
|
|
|
const formatTime = (time: string) => {
|
|
const [hours, minutes] = time.split(':').map(Number);
|
|
const period = hours >= 12 ? 'PM' : 'AM';
|
|
const displayHours = hours > 12 ? hours - 12 : hours === 0 ? 12 : hours;
|
|
return `${displayHours}:${minutes.toString().padStart(2, '0')} ${period}`;
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-10 animate-in fade-in duration-700 pb-10">
|
|
{/* Premium Header */}
|
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-6">
|
|
<div>
|
|
<h2 className="text-3xl font-black text-text tracking-tight text-capitalize uppercase">Faculty Schedule</h2>
|
|
<nav className="flex text-[0.65rem] font-black uppercase tracking-[2px] text-text-muted gap-2 items-center mt-1">
|
|
<span>NextGen Sync</span>
|
|
<ChevronRight size={12} className="text-text-muted/30" />
|
|
<span className="text-primary">Timetable & Events</span>
|
|
</nav>
|
|
</div>
|
|
<div className="flex gap-3">
|
|
<button className="flex items-center gap-2 px-5 py-2.5 bg-card border border-border text-text rounded-xl font-bold text-sm hover:bg-muted transition-all shadow-sm active:scale-95">
|
|
<Download size={18} />
|
|
Export iCal
|
|
</button>
|
|
<button
|
|
className="flex items-center gap-2 px-6 py-2.5 bg-primary text-white rounded-xl font-bold text-sm hover:bg-primary/90 transition-all shadow-lg shadow-blue-900/10 active:scale-95"
|
|
onClick={() => {
|
|
setForm({ title: '', description: '', event_date: selectedDate, event_type: 'general', start_time: '09:00', end_time: '10:00', location: '' });
|
|
setShowModal(true);
|
|
}}
|
|
>
|
|
<Plus size={18} />
|
|
Schedule Event
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats Bento Grid */}
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
{stats.map((stat) => (
|
|
<div key={stat.label} className="bg-card border border-border p-5 rounded-2xl flex items-center gap-4 hover:shadow-md transition-all group cursor-default">
|
|
<div className={`w-12 h-12 rounded-2xl flex items-center justify-center border group-hover:scale-110 transition-transform
|
|
${stat.color === 'blue' ? 'bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 border-blue-100 dark:border-blue-900/30' :
|
|
stat.color === 'emerald' ? 'bg-emerald-50 dark:bg-emerald-900/20 text-emerald-600 dark:text-emerald-400 border-emerald-100 dark:border-emerald-900/30' :
|
|
stat.color === 'orange' ? 'bg-orange-50 dark:bg-orange-900/20 text-orange-600 dark:text-orange-400 border-orange-100 dark:border-orange-900/30' :
|
|
'bg-purple-50 dark:bg-purple-900/20 text-purple-600 dark:text-purple-400 border-purple-100 dark:border-purple-900/30'}`}>
|
|
<stat.icon size={22} />
|
|
</div>
|
|
<div>
|
|
<p className="text-[0.65rem] font-black uppercase tracking-wider text-text-muted">{stat.label}</p>
|
|
<div className="flex items-baseline gap-2">
|
|
<p className="text-2xl font-black text-text">{stat.value}</p>
|
|
<span className="text-[0.6rem] font-bold text-text-muted">{stat.trend}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Control Bar */}
|
|
<div className="bg-card border border-border rounded-[2rem] p-2 shadow-sm flex flex-col lg:flex-row items-stretch lg:items-center gap-4">
|
|
<div className="flex items-center gap-2 bg-muted/50 rounded-2xl p-1 shrink-0 dark:bg-background/50">
|
|
<button className="p-2.5 bg-card rounded-xl shadow-sm border border-border text-text hover:bg-muted transition-all"><ChevronLeft size={18} /></button>
|
|
<div className="px-6 flex flex-col items-center">
|
|
<span className="text-[0.6rem] font-black uppercase tracking-widest text-text-muted">June 2026</span>
|
|
<span className="text-sm font-black text-text">Week 23</span>
|
|
</div>
|
|
<button className="p-2.5 bg-card rounded-xl shadow-sm border border-border text-text hover:bg-muted transition-all"><ChevronRight size={18} /></button>
|
|
</div>
|
|
|
|
<div className="relative group flex-1">
|
|
<Search size={18} className="absolute left-4 top-1/2 -translate-y-1/2 text-text-muted group-focus-within:text-primary transition-colors" />
|
|
<input
|
|
type="text"
|
|
className="w-full bg-muted/50 border border-border rounded-2xl pl-11 pr-4 py-3.5 text-sm font-medium focus:outline-none focus:ring-4 focus:ring-primary/5 focus:bg-card focus:border-primary transition-all dark:bg-background/50"
|
|
placeholder="Search events, topics or locations..."
|
|
value={searchQuery}
|
|
onChange={e => setSearchQuery(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<button className="px-5 py-3 bg-muted/50 hover:bg-muted text-text-muted rounded-2xl border border-border transition-all flex items-center justify-center gap-2 dark:bg-background/50">
|
|
<Filter size={18} />
|
|
<span className="text-[0.7rem] font-black uppercase tracking-widest text-text-muted">Monthly View</span>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Main Timeline Grid */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8">
|
|
|
|
{/* Timeline View (8/12) */}
|
|
<div className="lg:col-span-8 space-y-6">
|
|
<div className="flex items-center justify-between px-2">
|
|
<h3 className="text-lg font-black text-text uppercase tracking-tight">Transmission Timeline</h3>
|
|
<div className="flex gap-2">
|
|
<span className="px-3 py-1 bg-primary text-white rounded-lg text-[0.6rem] font-black uppercase tracking-widest">Today</span>
|
|
<span className="px-3 py-1 bg-muted text-text-muted border border-border rounded-lg text-[0.6rem] font-black uppercase tracking-widest">Upcoming</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="relative pl-12 space-y-8">
|
|
{/* Visual Line */}
|
|
<div className="absolute left-[23px] top-4 bottom-4 w-0.5 bg-muted"></div>
|
|
|
|
{loading ? (
|
|
<div className="py-20 text-center">
|
|
<div className="w-8 h-8 border-3 border-primary border-t-transparent rounded-full animate-spin mx-auto"></div>
|
|
</div>
|
|
) : filteredEvents.length === 0 ? (
|
|
<div className="py-20 text-center bg-card border border-dashed border-border rounded-[2.5rem]">
|
|
<p className="text-text-muted font-bold uppercase tracking-widest text-xs">Queue clear for this period</p>
|
|
</div>
|
|
) : filteredEvents.map((event) => {
|
|
const styles = getTypeStyles(event.event_type);
|
|
return (
|
|
<div key={event.id} className="relative group">
|
|
{/* Dot */}
|
|
<div className={`absolute left-[-35px] top-6 w-3 h-3 rounded-full border-2 border-border shadow-md z-10 transition-all group-hover:scale-125 ${styles.dot}`}></div>
|
|
|
|
<div className={`bg-card border border-border rounded-[2.5rem] p-8 hover:shadow-xl hover:shadow-blue-900/5 transition-all group relative overflow-hidden flex flex-col md:flex-row justify-between items-start md:items-center gap-6 ${event.event_type === 'holiday' ? 'bg-muted/30' : ''}`}>
|
|
<div className="absolute top-0 right-0 w-32 h-32 bg-muted rounded-bl-[4rem] -mr-16 -mt-16 group-hover:bg-blue-50/10 dark:group-hover:bg-blue-900/20 transition-colors"></div>
|
|
|
|
<div className="flex items-start gap-6 relative z-10">
|
|
<div className={`w-14 h-14 rounded-2xl ${styles.bg} ${styles.text} border ${styles.border} flex items-center justify-center group-hover:rotate-6 transition-transform shadow-sm`}>
|
|
{event.event_type === 'holiday' ? <Coffee size={24} /> : <BookOpen size={24} />}
|
|
</div>
|
|
<div>
|
|
<div className="flex items-center gap-3 mb-1">
|
|
<h4 className="text-xl font-black text-text group-hover:text-primary transition-colors">{event.title}</h4>
|
|
<span className={`px-2 py-0.5 rounded-lg text-[0.55rem] font-black uppercase tracking-widest border ${styles.bg} ${styles.text} ${styles.border}`}>
|
|
{event.event_type}
|
|
</span>
|
|
</div>
|
|
<p className="text-[0.85rem] text-text-muted font-medium leading-relaxed max-w-md line-clamp-1">{event.description || 'System scheduled event for current academic period.'}</p>
|
|
|
|
<div className="flex flex-wrap items-center gap-6 mt-4">
|
|
<div className="flex items-center gap-2 text-[0.7rem] font-bold text-text-muted uppercase tracking-widest">
|
|
<CalendarIcon size={14} className="text-text-muted/50" />
|
|
{new Date(event.event_date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}
|
|
</div>
|
|
<div className="flex items-center gap-2 text-[0.7rem] font-bold text-text-muted uppercase tracking-widest">
|
|
<Clock size={14} className="text-text-muted/50" />
|
|
{event.start_time} - {event.end_time}
|
|
</div>
|
|
<div className="flex items-center gap-2 text-[0.7rem] font-bold text-text-muted uppercase tracking-widest">
|
|
<MapPin size={14} className="text-text-muted/50" />
|
|
{event.location}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2 self-end md:self-center opacity-0 group-hover:opacity-100 transition-opacity relative z-10">
|
|
<button className="p-3 bg-card border border-border text-text-muted hover:text-primary hover:border-primary rounded-xl transition-all shadow-sm">
|
|
<Edit2 size={16} />
|
|
</button>
|
|
<button onClick={() => deleteEvent(event.id)} className="p-3 bg-card border border-border text-text-muted hover:text-rose-500 hover:border-rose-300 transition-all shadow-sm">
|
|
<Trash2 size={16} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right Sidebar (4/12) */}
|
|
<div className="lg:col-span-4 space-y-8">
|
|
{/* mini Calendar Card */}
|
|
<div className="bg-card border border-border rounded-[2.5rem] p-8 shadow-sm">
|
|
<div className="flex items-center justify-between mb-8">
|
|
<h4 className="text-[0.7rem] font-black uppercase tracking-[3px] text-text">Intelligence Log</h4>
|
|
<MoreHorizontal size={18} className="text-text-muted" />
|
|
</div>
|
|
<div className="space-y-4">
|
|
<div className="bg-muted/50 rounded-3xl p-6 border border-border text-center relative overflow-hidden group">
|
|
<div className="absolute top-0 right-0 w-24 h-24 bg-primary opacity-[0.02] rounded-bl-full"></div>
|
|
<p className="text-[0.65rem] font-black uppercase tracking-widest text-text-muted mb-1">{new Date().toLocaleDateString('en-US', { weekday: 'long' })}</p>
|
|
<h2 className="text-6xl font-black text-text tracking-tighter">{new Date().getDate()}</h2>
|
|
<p className="text-sm font-black text-primary uppercase tracking-[4px] mt-2">{new Date().toLocaleDateString('en-US', { month: 'long' })}</p>
|
|
</div>
|
|
<div className="grid grid-cols-7 gap-2 text-center">
|
|
{['M','T','W','T','F','S','S'].map((d, i) => (
|
|
<div key={i} className="text-[0.6rem] font-black text-text-muted/30 uppercase">{d}</div>
|
|
))}
|
|
{Array.from({length: 31}).map((_, i) => (
|
|
<div key={i} className={`aspect-square flex items-center justify-center text-[0.7rem] font-bold rounded-lg transition-all cursor-pointer ${i + 1 === new Date().getDate() ? 'bg-primary text-white shadow-lg' : 'text-text-muted hover:bg-muted'}`}>
|
|
{i + 1}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* AI Assistant Card */}
|
|
<div className="bg-[#002147] rounded-[2.5rem] p-8 text-white shadow-2xl relative overflow-hidden group">
|
|
<div className="absolute right-[-40px] bottom-[-40px] opacity-10 group-hover:rotate-12 transition-transform duration-700">
|
|
<Sparkles size={200} strokeWidth={1} />
|
|
</div>
|
|
<div className="relative z-10">
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<div className="p-2.5 bg-white/10 rounded-xl backdrop-blur-md text-blue-400">
|
|
<Bell size={20} />
|
|
</div>
|
|
<span className="text-[0.6rem] font-black uppercase tracking-[3px] text-white/40">NextGen Assist</span>
|
|
</div>
|
|
<h4 className="text-xl font-black mb-3 leading-tight tracking-tight">Your Thursday is looking optimized.</h4>
|
|
<p className="text-sm font-medium text-blue-50/70 leading-relaxed mb-8">
|
|
You have a <span className="text-white font-black">Meeting</span> with the Science Dept at 2 PM. Prep materials are synced.
|
|
</p>
|
|
<button className="w-full py-4 bg-white/10 hover:bg-white/20 text-white rounded-xl text-[0.65rem] font-black uppercase tracking-widest transition-all border border-white/10">
|
|
View Briefing
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Creation Modal */}
|
|
{showModal && (
|
|
<div className="fixed inset-0 bg-[#002147]/60 backdrop-blur-sm z-[150] flex items-center justify-center p-4">
|
|
<div className="bg-card rounded-[2.5rem] w-full max-w-xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200 max-h-[90vh] flex flex-col border border-border">
|
|
<div className="px-10 py-8 bg-muted/50 border-b border-border flex justify-between items-center relative overflow-hidden shrink-0">
|
|
<div className="absolute right-[-20px] top-[-20px] opacity-[0.03] rotate-12">
|
|
<CalendarDays size={180} />
|
|
</div>
|
|
<div className="relative z-10">
|
|
<h2 className="text-2xl font-black text-text tracking-tight">Establish Event</h2>
|
|
<p className="text-xs font-bold text-text-muted uppercase tracking-widest mt-1">Configure schedule intelligence</p>
|
|
</div>
|
|
<button onClick={() => setShowModal(false)} className="relative z-10 w-10 h-10 flex items-center justify-center rounded-xl bg-card border border-border text-text-muted hover:text-rose-500 transition-all shadow-sm">
|
|
<X size={20} />
|
|
</button>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="p-10 space-y-6 overflow-y-auto flex-1 custom-scrollbar">
|
|
<div className="space-y-1.5">
|
|
<label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1">Event Identifier</label>
|
|
<input className="form-input" value={form.title} onChange={e => setForm({...form, title: e.target.value})} placeholder="e.g., Departmental Sync: Term 3" required />
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-6">
|
|
<div className="space-y-1.5">
|
|
<label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1">Selected Date</label>
|
|
<div className="relative">
|
|
<CalendarIcon size={16} className="absolute left-5 top-1/2 -translate-y-1/2 text-text-muted/50" />
|
|
<input className="form-input pl-12" type="date" value={form.event_date} onChange={e => setForm({...form, event_date: e.target.value})} required />
|
|
</div>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1">Intel Category</label>
|
|
<select className="form-input appearance-none cursor-pointer" value={form.event_type} onChange={e => setForm({...form, event_type: e.target.value as any})}>
|
|
<option value="general">General Registry</option>
|
|
<option value="holiday">Institutional Holiday</option>
|
|
<option value="exam">Assessment Window</option>
|
|
<option value="meeting">Strategic Session</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-6">
|
|
<div className="space-y-1.5">
|
|
<label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1">Start Transmission</label>
|
|
<input className="form-input" type="time" value={form.start_time} onChange={e => setForm({...form, start_time: e.target.value})} />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1">End Transmission</label>
|
|
<input className="form-input" type="time" value={form.end_time} onChange={e => setForm({...form, end_time: e.target.value})} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-1.5">
|
|
<label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1">Physical/Digital Location</label>
|
|
<div className="relative">
|
|
<MapPin size={16} className="absolute left-5 top-1/2 -translate-y-1/2 text-text-muted/50" />
|
|
<input className="form-input pl-12" value={form.location} onChange={e => setForm({...form, location: e.target.value})} placeholder="e.g., Room 402 or Zoom Link" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-1.5">
|
|
<label className="text-[0.6rem] font-black uppercase tracking-[2px] text-text-muted px-1">Description Context</label>
|
|
<textarea className="form-input" rows={3} value={form.description} onChange={e => setForm({...form, description: e.target.value})} placeholder="Outline key session objectives..." />
|
|
</div>
|
|
|
|
<button type="submit" className="w-full py-5 bg-primary hover:bg-primary/90 text-white font-black uppercase tracking-widest text-xs rounded-[1.5rem] transition-all shadow-xl active:scale-95 flex items-center justify-center gap-3">
|
|
<CheckCircle2 size={18} />
|
|
Finalize Schedule Entry
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<style>{`
|
|
.custom-scrollbar::-webkit-scrollbar { width: 4px; }
|
|
.custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
|
|
.custom-scrollbar::-webkit-scrollbar-thumb { background: rgba(0, 71, 171, 0.1); border-radius: 10px; }
|
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover { background: rgba(0, 71, 171, 0.2); }
|
|
`}</style>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const mockEvents: Event[] = [
|
|
{ id: 1, title: 'Mathematics Midterm', event_date: '2026-06-15', event_type: 'exam', start_time: '10:00', end_time: '12:00', location: 'Hall B', status: 'upcoming' },
|
|
{ id: 2, title: 'Science Faculty Sync', event_date: '2026-06-12', event_type: 'meeting', start_time: '14:00', end_time: '15:30', location: 'Boardroom', status: 'upcoming' },
|
|
{ id: 3, title: 'Regional Holiday', event_date: '2026-06-10', event_type: 'holiday', status: 'upcoming', location: 'Institutional' },
|
|
{ id: 4, title: 'Physics Advanced Lab', event_date: '2026-06-05', event_type: 'class', start_time: '08:30', end_time: '10:30', location: 'Lab 3', status: 'upcoming' },
|
|
];
|