import React, { useState, useCallback } from 'react'; import { useParams, useNavigate } from 'react-router'; import {Button, Card, Form, Space, Tabs, Tag, Skeleton} from 'antd'; import { ArrowLeftOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; import api from '../../api'; import { message } from '../../ui/app-message'; import { useQuery } from '@tanstack/react-query'; import type { TeacherCandidateUser } from './teacher-candidate'; import { getErrorMessage } from '../../utils/error'; import { QueryErrorState } from '../../components/QueryState'; import { ClassAttendanceTab, ClassInfoTab, ClassScheduleTab, ClassStudentsTab, ClassTeachersTab, STATUS_MAP, type ClassDetail, type ClassTeacher, type StudentItem, type AttendanceSummary, type ClassScheduleItem, } from './ClassDetailTabs'; const ClassDetailPage: React.FC = () => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const [editForm] = Form.useForm(); const [editingInfo, setEditingInfo] = useState(false); // Student modal state const [studentModalOpen, setStudentModalOpen] = useState(false); const [allStudents, setAllStudents] = useState([]); const [selectedStudentIds, setSelectedStudentIds] = useState([]); const [addingStudents, setAddingStudents] = useState(false); const [addingTeacher, setAddingTeacher] = useState(false); // Teacher modal state const [teacherModalOpen, setTeacherModalOpen] = useState(false); const [teacherRole, setTeacherRole] = useState('subject_teacher'); const [teacherSubject, setTeacherSubject] = useState(''); const [teacherUserId, setTeacherUserId] = useState(); // Schedule & attendance state const [scheduleDateRange, setScheduleDateRange] = useState< [dayjs.Dayjs | null, dayjs.Dayjs | null] >([null, null]); const [attendanceDateRange, setAttendanceDateRange] = useState< [dayjs.Dayjs | null, dayjs.Dayjs | null] >([null, null]); const { data: detailResult = { detail: null, students: [], teachers: [] }, isLoading: detailLoading, isFetching: detailFetching, isError: detailError, refetch: refetchDetail, } = useQuery<{ detail: ClassDetail | null; students: ClassDetail['students']; teachers: ClassDetail['teachers']; }>({ queryKey: ['classes', 'detail', id], queryFn: async () => { const res = (await api.get(`/classes/${id}`)) as ClassDetail; return { detail: res, students: res.students || [], teachers: res.teachers || [] }; }, }); const detail = detailResult.detail; const students = detailResult.students ?? []; const teachers = detailResult.teachers ?? []; const loading = detailLoading || detailFetching; const fetchDetail = useCallback(() => refetchDetail(), [refetchDetail]); const { data: allUsers = [], isError: allUsersError, refetch: refetchUsers, } = useQuery({ queryKey: ['rbac', 'users', 'all'], queryFn: async () => { return (await api.get('/rbac/users')) as TeacherCandidateUser[]; }, }); const fetchUsers = useCallback(() => refetchUsers(), [refetchUsers]); const { data: schedules = [], isError: schedulesError, refetch: refetchSchedules, } = useQuery({ queryKey: ['classes', 'schedule', id, scheduleDateRange], queryFn: async () => { if (!id) return []; const params: Record = {}; if (scheduleDateRange?.[0]) params.startDate = scheduleDateRange[0].format('YYYY-MM-DD'); if (scheduleDateRange?.[1]) params.endDate = scheduleDateRange[1].format('YYYY-MM-DD'); return (await api.get(`/classes/${id}/schedule`, { params })) || []; }, }); const { data: attendanceSummary = null, isError: attendanceSummaryError, refetch: refetchAttendanceSummary, } = useQuery({ queryKey: ['classes', 'attendance-summary', id, attendanceDateRange], queryFn: async () => { if (!id) return null; const params: Record = {}; if (attendanceDateRange?.[0]) params.startDate = attendanceDateRange[0].format('YYYY-MM-DD'); if (attendanceDateRange?.[1]) params.endDate = attendanceDateRange[1].format('YYYY-MM-DD'); return ( (await api.get(`/classes/${id}/attendance-summary`, { params, })) || null ); }, }); const handleSaveInfo = async () => { try { const values = await editForm.validateFields(); await api.put(`/classes/${id}`, { name: values.name, code: values.code, classType: values.classType, startDate: values.startDate?.format('YYYY-MM-DD'), endDate: values.endDate?.format('YYYY-MM-DD'), maxStudents: values.maxStudents, status: values.status, notes: values.notes, }); setEditingInfo(false); fetchDetail(); message.success('已更新'); } catch (e: unknown) { message.error(getErrorMessage(e, '更新失败')); } }; const handleRemoveStudent = async (studentId: number) => { try { await api.delete(`/classes/${id}/students/${studentId}`); fetchDetail(); message.success('已移除'); } catch (e: unknown) { message.error(getErrorMessage(e, '移除失败')); } }; const handleAddStudents = async () => { if (!selectedStudentIds.length) return; setAddingStudents(true); try { await api.post(`/classes/${id}/students`, { studentIds: selectedStudentIds }); setStudentModalOpen(false); setSelectedStudentIds([]); fetchDetail(); message.success('已添加'); } catch (e: unknown) { message.error(getErrorMessage(e, '添加失败')); } finally { setAddingStudents(false); } }; const handleAddTeacher = async () => { if (!teacherUserId) return; setAddingTeacher(true); try { await api.post(`/classes/${id}/teachers`, { userId: teacherUserId, roleType: teacherRole, subject: teacherSubject || undefined, }); setTeacherModalOpen(false); fetchDetail(); message.success('已添加'); } catch (e: unknown) { message.error(getErrorMessage(e, '添加失败')); } finally { setAddingTeacher(false); } }; const handleRemoveTeacher = async (userId: number) => { try { await api.delete(`/classes/${id}/teachers/${userId}`); fetchDetail(); message.success('已移除'); } catch (e: unknown) { message.error(getErrorMessage(e, '移除失败')); } }; const openStudentModal = async () => { try { const res = (await api.get('/students', { params: { includeArchived: 'false' }, })) as StudentItem[]; setAllStudents(res || []); setSelectedStudentIds([]); setStudentModalOpen(true); } catch (e: unknown) { message.error(getErrorMessage(e, '加载学员列表失败')); } }; const openTeacherModal = async () => { try { await fetchUsers(); setTeacherUserId(undefined); setTeacherRole('subject_teacher'); setTeacherSubject(''); setTeacherModalOpen(true); } catch (e: unknown) { message.error(getErrorMessage(e, '加载用户列表失败')); } }; const getTeacherName = (teacher: ClassTeacher) => allUsers.find((user) => user.id === teacher.userId)?.name?.trim() || '-'; if (!detail) { if (loading) { return (
); } if (detailError) { return ( void refetchDetail()} /> ); } return null; } return (