Replace lucide-react icon imports with @mui/icons-material equivalents across all timetable pages and common Modal component. Icons replaced: - lucide-react Check, X, UserPlus, Users, Filter, Search -> MUI equivalents - lucide-react ChevronDown, ChevronLeft, ChevronRight -> MUI equivalents - lucide-react BookOpen, Clock, GraduationCap, School -> MUI equivalents - lucide-react Calendar, MapPin, Edit, Trash2, Plus -> MUI equivalents - lucide-react ArrowLeft, FileText, CheckCircle, XCircle -> MUI equivalents Fixes build error: Failed to resolve import 'lucide-react'
325 lines
12 KiB
TypeScript
325 lines
12 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { useParams, Link, useNavigate } from 'react-router-dom';
|
|
import { AccessTimeIcon, AddIcon, ArrowBackIcon, CalendarTodayIcon, DeleteIcon, EditIcon, MenuBookIcon, PeopleIcon } from '@mui/icons-material';
|
|
import useTimetableStore from '../../stores/timetableStore';
|
|
import { useProfile } from '../../contexts/ProfileContext';
|
|
import Modal from '../../components/common/Modal';
|
|
|
|
const ClassDetailPage: React.FC = () => {
|
|
const { classId } = useParams<{ classId: string }>();
|
|
const navigate = useNavigate();
|
|
const { profile } = useProfile();
|
|
const {
|
|
currentClass,
|
|
timetables,
|
|
enrolledStudents,
|
|
classTeachers,
|
|
classDetailLoading,
|
|
classDetailError,
|
|
fetchClassDetail,
|
|
deleteClass,
|
|
clearCurrentClass,
|
|
} = useTimetableStore();
|
|
|
|
const [activeTab, setActiveTab] = useState<'timetables' | 'students' | 'teachers'>('timetables');
|
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (classId) {
|
|
fetchClassDetail(classId);
|
|
}
|
|
return () => {
|
|
clearCurrentClass();
|
|
};
|
|
}, [classId, fetchClassDetail, clearCurrentClass]);
|
|
|
|
const handleDeleteClass = async () => {
|
|
if (!classId) return;
|
|
await deleteClass(classId);
|
|
navigate('/timetable/classes');
|
|
};
|
|
|
|
const isOwner = currentClass?.created_by === profile?.id;
|
|
const isTeacher = classTeachers.some(t => t.teacher_id === profile?.id && t.is_primary);
|
|
|
|
if (classDetailLoading) {
|
|
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 (classDetailError || !currentClass) {
|
|
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 Class</h2>
|
|
<p className="text-red-600">{classDetailError || 'Class 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"
|
|
className="inline-flex items-center gap-2 text-gray-500 hover:text-gray-700 mb-4"
|
|
>
|
|
<ArrowBackIcon size={18} />
|
|
Back to Classes
|
|
</Link>
|
|
|
|
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
|
|
<div>
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<h1 className="text-3xl font-bold text-gray-900">{currentClass.name}</h1>
|
|
<span className="px-3 py-1 bg-blue-100 text-blue-700 text-sm font-medium rounded-full">
|
|
{currentClass.subject}
|
|
</span>
|
|
</div>
|
|
<p className="text-gray-500">
|
|
{currentClass.school_year} • {currentClass.academic_term}
|
|
</p>
|
|
</div>
|
|
|
|
{(isOwner || isTeacher) && (
|
|
<div className="flex items-center gap-2">
|
|
<Link
|
|
to={`/timetable/classes/${classId}/edit`}
|
|
className="inline-flex items-center gap-2 px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
|
|
>
|
|
<EditIcon size={18} />
|
|
Edit
|
|
</Link>
|
|
<button
|
|
onClick={() => setShowDeleteModal(true)}
|
|
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"
|
|
>
|
|
<DeleteIcon size={18} />
|
|
Delete
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats */}
|
|
<div className="grid grid-cols-3 gap-4 mb-8">
|
|
<div className="bg-white p-4 rounded-xl shadow-sm border border-gray-200">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-blue-50 rounded-lg">
|
|
<CalendarTodayIcon className="text-blue-600" size={20} />
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-gray-500">Timetables</p>
|
|
<p className="text-xl font-semibold text-gray-900">{currentClass.timetable_count}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="bg-white p-4 rounded-xl shadow-sm border border-gray-200">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-green-50 rounded-lg">
|
|
<PeopleIcon className="text-green-600" size={20} />
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-gray-500">Students</p>
|
|
<p className="text-xl font-semibold text-gray-900">{currentClass.student_count}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="bg-white p-4 rounded-xl shadow-sm border border-gray-200">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 bg-purple-50 rounded-lg">
|
|
<MenuBookIcon className="text-purple-600" size={20} />
|
|
</div>
|
|
<div>
|
|
<p className="text-sm text-gray-500">Teachers</p>
|
|
<p className="text-xl font-semibold text-gray-900">{classTeachers.length}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<div className="border-b border-gray-200 mb-6">
|
|
<div className="flex gap-6">
|
|
<button
|
|
onClick={() => setActiveTab('timetables')}
|
|
className={`pb-3 px-1 text-sm font-medium border-b-2 transition-colors ${
|
|
activeTab === 'timetables'
|
|
? 'border-blue-600 text-blue-600'
|
|
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
}`}
|
|
>
|
|
Timetables
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('students')}
|
|
className={`pb-3 px-1 text-sm font-medium border-b-2 transition-colors ${
|
|
activeTab === 'students'
|
|
? 'border-blue-600 text-blue-600'
|
|
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
}`}
|
|
>
|
|
Students ({enrolledStudents.length})
|
|
</button>
|
|
<button
|
|
onClick={() => setActiveTab('teachers')}
|
|
className={`pb-3 px-1 text-sm font-medium border-b-2 transition-colors ${
|
|
activeTab === 'teachers'
|
|
? 'border-blue-600 text-blue-600'
|
|
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
}`}
|
|
>
|
|
Teachers ({classTeachers.length})
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Tab Content */}
|
|
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
|
{activeTab === 'timetables' && (
|
|
<div>
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h2 className="text-lg font-semibold text-gray-900">Timetables</h2>
|
|
{(isOwner || isTeacher) && (
|
|
<Link
|
|
to={`/timetable/classes/${classId}/timetables/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"
|
|
>
|
|
<AddIcon size={18} />
|
|
Add Timetable
|
|
</Link>
|
|
)}
|
|
</div>
|
|
|
|
{timetables.length === 0 ? (
|
|
<div className="text-center py-12 text-gray-500">
|
|
<AccessTimeIcon size={48} className="mx-auto mb-4 opacity-50" />
|
|
<p className="text-lg font-medium mb-2">No timetables yet</p>
|
|
<p>Create a timetable to start scheduling lessons</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{timetables.map((timetable) => (
|
|
<Link
|
|
key={timetable.id}
|
|
to={`/timetable/timetables/${timetable.id}`}
|
|
className="flex items-center justify-between p-4 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
|
|
>
|
|
<div>
|
|
<h3 className="font-medium text-gray-900">{timetable.name}</h3>
|
|
<p className="text-sm text-gray-500">
|
|
{timetable.lesson_count} lessons
|
|
{timetable.is_recurring && ' • Recurring'}
|
|
</p>
|
|
</div>
|
|
<ArrowBackIcon className="rotate-180 text-gray-400" size={18} />
|
|
</Link>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'students' && (
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-gray-900 mb-4">Enrolled Students</h2>
|
|
{enrolledStudents.length === 0 ? (
|
|
<div className="text-center py-12 text-gray-500">
|
|
<PeopleIcon size={48} className="mx-auto mb-4 opacity-50" />
|
|
<p className="text-lg font-medium mb-2">No students enrolled</p>
|
|
<p>Students can request enrollment or be added by teachers</p>
|
|
</div>
|
|
) : (
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
{enrolledStudents.map((student) => (
|
|
<div
|
|
key={student.user_id}
|
|
className="flex items-center gap-3 p-4 border border-gray-200 rounded-lg"
|
|
>
|
|
<div className="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center">
|
|
<span className="text-gray-600 font-medium">
|
|
{student.full_name.charAt(0)}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<p className="font-medium text-gray-900">{student.full_name}</p>
|
|
<p className="text-sm text-gray-500">{student.email}</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'teachers' && (
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-gray-900 mb-4">Teachers</h2>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
{classTeachers.map((teacher) => (
|
|
<div
|
|
key={teacher.teacher_id}
|
|
className="flex items-center gap-3 p-4 border border-gray-200 rounded-lg"
|
|
>
|
|
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
|
|
<span className="text-blue-600 font-medium">
|
|
{teacher.full_name.charAt(0)}
|
|
</span>
|
|
</div>
|
|
<div className="flex-1">
|
|
<p className="font-medium text-gray-900">{teacher.full_name}</p>
|
|
<p className="text-sm text-gray-500">{teacher.email}</p>
|
|
</div>
|
|
{teacher.is_primary && (
|
|
<span className="px-2 py-1 bg-blue-100 text-blue-700 text-xs font-medium rounded-full">
|
|
Primary
|
|
</span>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Delete Modal */}
|
|
<Modal
|
|
isOpen={showDeleteModal}
|
|
onClose={() => setShowDeleteModal(false)}
|
|
title="Delete Class"
|
|
>
|
|
<div className="p-6">
|
|
<p className="text-gray-600 mb-6">
|
|
Are you sure you want to delete "{currentClass.name}"? This action cannot be undone and will remove all timetables, lessons, and whiteboards associated with this class.
|
|
</p>
|
|
<div className="flex justify-end gap-3">
|
|
<button
|
|
onClick={() => setShowDeleteModal(false)}
|
|
className="px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-lg transition-colors"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
onClick={handleDeleteClass}
|
|
className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors"
|
|
>
|
|
Delete Class
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ClassDetailPage;
|