"use client" import Link from "next/link" import { useTranslations } from "next-intl" import { Award, AlertTriangle, Lightbulb, FileText, History, ArrowRight } from "lucide-react" import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/shared/components/ui/card" import { Badge } from "@/shared/components/ui/badge" import { Button } from "@/shared/components/ui/button" import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/components/ui/tooltip" import { EmptyState } from "@/shared/components/ui/empty-state" import { WidgetBoundary } from "@/shared/components/widget-boundary" import { formatDate } from "@/shared/lib/utils" import { MasteryRadarChart } from "./mastery-radar-chart" import { getConfidenceLevel, confidenceBadgeVariant, type ConfidenceLevel, } from "./confidence-utils" import { getDiagnosticRoleConfig, type DiagnosticRole } from "../role-config" import type { DiagnosticReportWithDetails, MasteryRadarPoint, StudentMasterySummary } from "../types" interface StudentDiagnosticViewProps { summary: StudentMasterySummary | null reports: DiagnosticReportWithDetails[] classAverageMastery?: MasteryRadarPoint[] /** * v4-P2-2: 角色配置驱动。 * 组件内部根据 role 查找 DIAGNOSTIC_ROLE_CONFIG 获取 practiceHrefBase 等角色差异配置。 * 新增角色只需在 role-config.ts 中添加配置项,无需修改组件 props。 */ role?: DiagnosticRole /** * @deprecated v4-P2-2: 请改用 `role` prop。保留向后兼容,若同时传入则 role 优先。 */ practiceHrefBase?: string | null } export function StudentDiagnosticView({ summary, reports, classAverageMastery, role = "student", practiceHrefBase, }: StudentDiagnosticViewProps) { const t = useTranslations("diagnostic") // v4-P2-2: 角色配置驱动,role prop 优先于 deprecated practiceHrefBase const resolvedPracticeHrefBase = practiceHrefBase ?? getDiagnosticRoleConfig(role).practiceHrefBase if (!summary) { return ( ) } const radarData: MasteryRadarPoint[] = summary.allMastery.map((m) => { const classAvg = classAverageMastery?.find((c) => c.knowledgePoint === m.knowledgePointName) return { knowledgePoint: m.knowledgePointName, student: Math.round(m.masteryLevel * 100) / 100, classAverage: classAvg?.classAverage, } }) const publishedReports = reports.filter((r) => r.status === "published") // v4-P1-3: 移除草稿回退逻辑,仅展示已发布报告 // 调用方(学生/家长页面)已传 status: "published" 过滤,此处双重保障 const latestReport = publishedReports[0] ?? null const statusLabel = (status: string): string => { if (status === "draft") return t("status.draft") if (status === "published") return t("status.published") if (status === "archived") return t("status.archived") return status } const typeLabel = (reportType: string): string => { if (reportType === "individual") return t("type.individual") if (reportType === "class") return t("type.class") if (reportType === "grade") return t("type.grade") return reportType } // v4-P3-7: 置信度标签与提示 const confidenceLabel = (level: ConfidenceLevel): string => { if (level === "high") return t("reportList.confidenceHigh") if (level === "medium") return t("reportList.confidenceMedium") if (level === "low") return t("reportList.confidenceLow") return t("reportList.confidenceInsufficient") } const confidenceHint = (level: ConfidenceLevel): string => { if (level === "high") return t("reportList.confidenceHighHint") if (level === "medium") return t("reportList.confidenceMediumHint") if (level === "low") return t("reportList.confidenceLowHint") return t("reportList.confidenceInsufficient") } return (
{/* 概览卡片 */}
{t("summary.student")}

{summary.studentName}

{t("summary.overallMastery")}

{summary.averageMastery.toFixed(1)}%

{t("summary.strengths")}

{summary.strengths.length}

{t("summary.weaknesses")}

{summary.weaknesses.length}

{/* v2-P1-6: 雷达图区块独立 Error Boundary */} {/* v2-P1-6: 强项 / 弱项区块独立 Error Boundary */}
{t("strengths.title")} {t("studentDiagnostic.strengthsDescription")} {summary.strengths.length === 0 ? (

{t("studentDiagnostic.noStrengths")}

) : (
    {summary.strengths.map((m) => (
  • {m.knowledgePointName} {m.masteryLevel.toFixed(1)}%
  • ))}
)}
{t("weaknesses.title")} {t("studentDiagnostic.weaknessesDescription")} {summary.weaknesses.length === 0 ? (

{t("studentDiagnostic.noWeaknesses")}

) : (
    {summary.weaknesses.map((m) => (
  • {m.knowledgePointName} {m.masteryLevel.toFixed(1)}%
    {resolvedPracticeHrefBase ? ( ) : null}
  • ))}
)}
{/* v2-P1-6: 最新报告区块独立 Error Boundary */} {latestReport ? ( {t("studentDiagnostic.diagnosticReportTitle")} {statusLabel(latestReport.status)} {(() => { const level = getConfidenceLevel(latestReport) return ( {confidenceLabel(level)} {confidenceHint(level)} ) })()} {t("studentDiagnostic.reportMeta", { period: latestReport.period ?? "-", score: latestReport.overallScore?.toFixed(1) ?? "-", })} {latestReport.summary ? (

{latestReport.summary}

) : null} {latestReport.recommendations && latestReport.recommendations.length > 0 ? (

{t("report.recommendations")}

    {latestReport.recommendations.map((rec, i) => (
  • • {rec}
  • ))}
) : null}
) : null} {/* v2-P1-6: 历史报告区块独立 Error Boundary */} {publishedReports.length > 1 ? ( {t("report.history")} {t("studentDiagnostic.historyDescription")}
{publishedReports.map((r) => (
{r.period ?? t("studentDiagnostic.untitledPeriod")} {typeLabel(r.reportType)}

{r.overallScore !== null ? t("studentDiagnostic.historyReportMeta", { date: formatDate(r.createdAt), score: r.overallScore.toFixed(1), }) : formatDate(r.createdAt)}

))}
) : null}
) }