app/src/pages/timetable/TimetablePage.tsx

196 lines
7.5 KiB
TypeScript

import React, { useEffect } from 'react';
import { useParams, Link, useNavigate } from 'react-router-dom';
import { AccessTime, Add, ArrowBack, CalendarToday, Delete, Edit, KeyboardArrowLeft, KeyboardArrowRight, LocationOn } from '@mui/icons-material';
import useTimetableStore from '../../stores/timetableStore';
import { useUser } from '../../contexts/UserContext';
import { format, parseISO, addDays, startOfWeek, isSameDay } from 'date-fns';
const TimetablePage: React.FC = () => {
const { timetableId } = useParams<{ timetableId: string }>();
const navigate = useNavigate();
const { profile } = useUser();
const {
currentTimetable,
currentLessons,
timetableDetailLoading,
timetableDetailError,
fetchTimetable,
deleteTimetable,
clearCurrentTimetable,
} = useTimetableStore();
useEffect(() => {
if (timetableId) {
fetchTimetable(timetableId);
}
return () => {
clearCurrentTimetable();
};
}, [timetableId, fetchTimetable, clearCurrentTimetable]);
const handleDeleteTimetable = async () => {
if (!timetableId) return;
if (confirm('Are you sure you want to delete this timetable?')) {
await deleteTimetable(timetableId);
navigate(`/timetable/classes/${currentTimetable?.class_id}`);
}
};
// Group lessons by day
const lessonsByDay = (currentLessons || []).reduce((acc, lesson) => {
const date = lesson.day_of_week || format(parseISO(lesson.start_time), 'yyyy-MM-dd');
if (!acc[date]) acc[date] = [];
acc[date].push(lesson);
return acc;
}, {} as Record<string, typeof currentLessons>);
// Sort lessons within each day by start time
Object.keys(lessonsByDay).forEach(day => {
lessonsByDay[day].sort((a, b) =>
new Date(a.start_time).getTime() - new Date(b.start_time).getTime()
);
});
if (timetableDetailLoading) {
return (
<div className="flex justify-center items-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
);
}
if (timetableDetailError || !currentTimetable) {
return (
<div className="container mx-auto px-4 py-6">
<div className="bg-red-50 border border-red-200 rounded-xl p-6">
<h2 className="text-lg font-semibold text-red-800 mb-2">Error Loading Timetable</h2>
<p className="text-red-600">{timetableDetailError || 'Timetable not found'}</p>
<Link to="/timetable/classes" className="text-blue-600 hover:underline mt-4 inline-block">
Back to Classes
</Link>
</div>
</div>
);
}
return (
<div className="container mx-auto px-4 py-6 max-w-6xl">
{/* Header */}
<div className="mb-6">
<Link
to={`/timetable/classes/${currentTimetable.class_id}`}
className="inline-flex items-center gap-2 text-gray-500 hover:text-gray-700 mb-4"
>
<ArrowBack sx={{ fontSize: 18 }} />
Back to Class
</Link>
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
<div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">{currentTimetable.name}</h1>
<div className="flex items-center gap-4 text-gray-500">
<span className="flex items-center gap-1">
<CalendarToday sx={{ fontSize: 16 }} />
{format(parseISO(currentTimetable.effective_from), 'MMM d, yyyy')}
{currentTimetable.effective_until && ` - ${format(parseISO(currentTimetable.effective_until), 'MMM d, yyyy')}`}
</span>
{currentTimetable.is_recurring && (
<span className="px-2 py-1 bg-green-100 text-green-700 text-xs font-medium rounded-full">
Recurring
</span>
)}
</div>
</div>
<div className="flex items-center gap-2">
<Link
to={`/timetable/timetables/${timetableId}/lessons/new`}
className="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
<Add sx={{ fontSize: 18 }} />
Add Lesson
</Link>
<button
onClick={handleDeleteTimetable}
className="inline-flex items-center gap-2 px-4 py-2 bg-red-100 text-red-700 rounded-lg hover:bg-red-200 transition-colors"
>
<Delete sx={{ fontSize: 18 }} />
Delete
</button>
</div>
</div>
</div>
{/* Lessons List */}
<div className="bg-white rounded-xl shadow-sm border border-gray-200">
<div className="p-6 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-900">
Lessons ({currentLessons.length})
</h2>
</div>
{currentLessons.length === 0 ? (
<div className="p-12 text-center text-gray-500">
<AccessTime sx={{ fontSize: 48 }} className="mx-auto mb-4 opacity-50" />
<p className="text-lg font-medium mb-2">No lessons scheduled</p>
<p>Add lessons to build your timetable</p>
</div>
) : (
<div className="divide-y divide-gray-200">
{Object.entries(lessonsByDay).map(([day, lessons]) => (
<div key={day} className="p-6">
<h3 className="text-sm font-medium text-gray-500 uppercase tracking-wide mb-4">
{currentTimetable.is_recurring
? `Day ${day}`
: format(parseISO(day), 'EEEE, MMMM d, yyyy')}
</h3>
<div className="space-y-3">
{lessons.map((lesson) => (
<Link
key={lesson.id}
to={`/timetable/lessons/${lesson.id}`}
className="flex items-center gap-4 p-4 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors"
>
<div className="flex-shrink-0 w-16 text-center">
<div className="text-sm font-medium text-gray-900">
{format(parseISO(lesson.start_time), 'HH:mm')}
</div>
<div className="text-xs text-gray-500">
{format(parseISO(lesson.end_time), 'HH:mm')}
</div>
</div>
<div className="flex-1 min-w-0">
<h4 className="font-medium text-gray-900 truncate">
{lesson.title}
</h4>
{lesson.description && (
<p className="text-sm text-gray-500 truncate">
{lesson.description}
</p>
)}
</div>
{lesson.location && (
<div className="flex items-center gap-1 text-sm text-gray-500">
<LocationOn sx={{ fontSize: 14 }} />
{lesson.location}
</div>
)}
{lesson.room && (
<span className="px-2 py-1 bg-blue-100 text-blue-700 text-xs font-medium rounded">
Room {lesson.room}
</span>
)}
</Link>
))}
</div>
</div>
))}
</div>
)}
</div>
</div>
);
};
export default TimetablePage;