"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")}
) : (
)}
{/* 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}
)
}