"use client" import { useState, useTransition } from "react" import { useRouter } from "next/navigation" import { toast } from "sonner" import { useTranslations } from "next-intl" import { BookOpen, CheckCircle2, XCircle } from "lucide-react" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/shared/components/ui/alert-dialog" import { Badge } from "@/shared/components/ui/badge" import { Button } from "@/shared/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card" import { EmptyState } from "@/shared/components/ui/empty-state" import { Label } from "@/shared/components/ui/label" import { Textarea } from "@/shared/components/ui/textarea" import { COURSE_SELECTION_STATUS_BADGE_VARIANTS, COURSE_SELECTION_STATUS_LABEL_KEYS, ELECTIVE_STATUS_BADGE_VARIANTS, ELECTIVE_STATUS_LABEL_KEYS, SELECTION_MODE_LABEL_KEYS, } from "../constants" import type { CourseSelectionWithDetails, ElectiveCourseWithDetails, } from "../types" import { selectCourseAction, dropCourseAction } from "../actions" /** * 已选课程区块(我的选课) * - 独立 Suspense 边界内的客户端组件 * - 仅负责"退课"操作与展示 */ export function StudentMySelectionsSection({ mySelections, }: { mySelections: CourseSelectionWithDetails[] }) { const router = useRouter() const t = useTranslations("elective") const [pendingId, setPendingId] = useState(null) const [isPending, startTransition] = useTransition() // P2-4:退课理由输入(按课程 ID 隔离,便于多卡片独立填写) const [dropReasonMap, setDropReasonMap] = useState>({}) const activeSelections = mySelections.filter((s) => ["selected", "enrolled", "waitlist"].includes(s.status) ) const handleDrop = (courseId: string) => { setPendingId(courseId) startTransition(async () => { const formData = new FormData() formData.set("courseId", courseId) // P2-4:透传退课理由 const reason = dropReasonMap[courseId] if (reason && reason.trim().length > 0) { formData.set("dropReason", reason.trim()) } const res = await dropCourseAction(null, formData) if (res.success) { toast.success(res.message || t("student.dropSuccess")) router.refresh() // 清空该课程的退课理由 setDropReasonMap((prev) => { const next = { ...prev } delete next[courseId] return next }) } else { toast.error(res.message ?? t("errors.unexpected")) } setPendingId(null) }) } return (

{t("student.mySelections")}

{activeSelections.length}
{activeSelections.length === 0 ? ( ) : (
{activeSelections.map((sel) => ( {sel.courseName ?? t("errors.notFound")} {t(COURSE_SELECTION_STATUS_LABEL_KEYS[sel.status])} {sel.courseCapacity !== null && sel.courseEnrolledCount !== null ? (

{t("fields.enrolled")}: {sel.courseEnrolledCount}/{sel.courseCapacity}

) : null} {sel.lotteryRank ? (

#{sel.lotteryRank}

) : null} {t("student.confirmDrop")} {t("student.confirmDrop")} {/* P2-4:可选退课理由输入 */}