import { Button, Checkbox, Divider, Select, Space, Table, Typography, message } from 'antd' import { useEffect, useMemo, useState } from 'react' import { examApi } from '../api/client' import type { Exam, ReviewStatus } from '../types' import { EXAM_TYPE_LABELS, REVIEW_STATUS_OPTIONS } from '../types' import ReviewBarChart from './ReviewBarChart' import ReviewSubjectDetail from './ReviewSubjectDetail' function apiErrorMessage(err: unknown, fallback: string): string { if (err && typeof err === 'object' && 'response' in err) { const detail = (err as { response?: { data?: { detail?: unknown } } }).response?.data?.detail if (typeof detail === 'string') return detail if (Array.isArray(detail)) { return detail.map((item) => item?.msg || String(item)).join(';') } } return fallback } function firstSubjectWithReview(exams: Exam[]): string | null { const names = new Set() for (const exam of exams) { for (const score of exam.scores) { if (score.review_statuses?.length) { names.add(score.subject_name || `科目${score.subject_id}`) } } } const sorted = Array.from(names).sort((a, b) => a.localeCompare(b, 'zh-CN')) return sorted[0] || null } interface Props { exams: Exam[] onRefresh: () => void } export default function ExamReviewPanel({ exams, onRefresh }: Props) { const [examId, setExamId] = useState() const [statusMap, setStatusMap] = useState>({}) const [saving, setSaving] = useState(false) const [selectedSubject, setSelectedSubject] = useState(null) const examOptions = useMemo( () => exams.map((exam) => ({ value: exam.id, label: `${exam.exam_date} · ${EXAM_TYPE_LABELS[exam.exam_type]}${exam.title ? ` · ${exam.title}` : ''}`, })), [exams], ) const selectedExam = exams.find((e) => e.id === examId) useEffect(() => { if (!examId && exams.length) { setExamId(exams[0].id) } }, [examId, exams]) useEffect(() => { const defaultSubject = firstSubjectWithReview(exams) if (defaultSubject) { setSelectedSubject((prev) => prev ?? defaultSubject) } }, [exams]) useEffect(() => { if (!selectedExam) { setStatusMap({}) return } const next: Record = {} for (const score of selectedExam.scores) { next[score.subject_id] = [...(score.review_statuses || [])] } setStatusMap(next) }, [selectedExam]) const handleSave = async () => { if (!selectedExam) { message.warning('请选择考试') return } setSaving(true) try { await examApi.update(selectedExam.id, { scores: selectedExam.scores.map((score) => ({ subject_id: score.subject_id, total_score: Number(score.total_score), obtained_score: Number(score.obtained_score), review_statuses: statusMap[score.subject_id] || [], })), }) message.success('复盘已保存') onRefresh() } catch (err) { message.error(apiErrorMessage(err, '保存失败')) } finally { setSaving(false) } } if (exams.length === 0) { return ( 请先录入至少一次考试成绩,再进行复盘 ) } return (
填写复盘