import type { Metadata } from "next" import type { JSX } from "react" import { Suspense } from "react" import { BarChart3 } from "lucide-react" import { getTranslations } from "next-intl/server" import { requirePermission } from "@/shared/lib/auth-guard" import { Permissions } from "@/shared/types/permissions" import { EmptyState } from "@/shared/components/ui/empty-state" import { Skeleton } from "@/shared/components/ui/skeleton" import { getParam, type SearchParams } from "@/shared/lib/search-params" import { getClassIdsByGradeIds, getStudentIdsByClassIds, } from "@/modules/classes/data-access" import { getTeacherClassPracticeOverviews, getClassStudentPracticeSummaries, getPracticeTypeBreakdown, getClassKnowledgePointWeakness, getStudentsWithoutPractice, getStudentNameMap, } from "@/modules/adaptive-practice/data-access-analytics" import { PracticeOverviewStatsCards } from "@/modules/adaptive-practice/components/practice-overview-stats-cards" import { ClassPracticeComparisonTable } from "@/modules/adaptive-practice/components/class-practice-comparison-table" import { PracticeTypeBreakdownChart } from "@/modules/adaptive-practice/components/practice-type-breakdown-chart" import { ClassKnowledgePointWeaknessChart } from "@/modules/adaptive-practice/components/class-knowledge-point-weakness-chart" import { StudentPracticeRankingTable } from "@/modules/adaptive-practice/components/student-practice-ranking-table" import { InactiveStudentsAlert } from "@/modules/adaptive-practice/components/inactive-students-alert" import { ClassFilter } from "@/modules/error-book/components/class-filter" import type { ClassErrorOverview } from "@/modules/error-book/types" export const dynamic = "force-dynamic" export async function generateMetadata(): Promise { const t = await getTranslations("practice") return { title: `${t("teacher.title")} - Next_Edu`, description: t("teacher.description"), } } async function TeacherPracticeContent({ searchParams, }: { searchParams: Promise }): Promise { const ctx = await requirePermission(Permissions.ADAPTIVE_PRACTICE_READ) const t = await getTranslations("practice") const params = await searchParams const classIds = ctx.dataScope.type === "class_taught" ? ctx.dataScope.classIds : [] const gradeIds = ctx.dataScope.type === "grade_managed" ? ctx.dataScope.gradeIds : [] if (classIds.length === 0 && gradeIds.length === 0 && ctx.dataScope.type !== "all") { return (

{t("teacher.title")}

{t("teacher.description")}

) } // 年级主任/教研组长:展开年级为班级 let targetClassIds = classIds if (gradeIds.length > 0) { const gradeClassIds = await getClassIdsByGradeIds(gradeIds) targetClassIds = [...new Set([...classIds, ...gradeClassIds])] } // 获取所有学生 ID const allStudentIds = await getStudentIdsByClassIds(targetClassIds) if (allStudentIds.length === 0) { return (

{t("teacher.title")}

{t("teacher.description")}

) } // 解析 URL 参数:班级筛选 const classParam = getParam(params, "classId") const effectiveClassId = classParam ?? "all" // 班级概览(用于班级筛选器显示) const classOverviews = await getTeacherClassPracticeOverviews(targetClassIds) // 构造 ClassFilter 所需的数据格式 const classFilterData: ClassErrorOverview[] = classOverviews.map((c) => ({ classId: c.classId, className: c.className, studentCount: c.totalStudents, totalErrorItems: c.totalSessions, dueReviewCount: 0, averageErrorPerStudent: c.totalStudents > 0 ? c.totalSessions / c.totalStudents : 0, averageMasteryRate: c.averageAccuracy, })) // 确定查询的班级范围 const queryClassIds = effectiveClassId === "all" ? targetClassIds : [effectiveClassId] const queryStudentIds = await getStudentIdsByClassIds(queryClassIds) // 并行查询所有统计数据 const [typeBreakdown, studentSummaries, nameMap, inactiveIds] = await Promise.all([ getPracticeTypeBreakdown(queryStudentIds), // 按班级查询学生摘要(如果是单班级视图,直接查询;如果是全部视图,按班级逐个查询后合并) effectiveClassId === "all" ? getClassStudentPracticeSummariesForClasses(queryClassIds) : getClassStudentPracticeSummaries(effectiveClassId), getStudentNameMap(queryStudentIds), effectiveClassId === "all" ? getInactiveStudentsForClasses(queryClassIds) : getStudentsWithoutPractice(effectiveClassId), ]) // 聚合统计 const totalSessions = classOverviews.reduce((sum, c) => sum + c.totalSessions, 0) const totalAnswered = classOverviews.reduce((sum, c) => sum + c.totalQuestionsAnswered, 0) const totalCorrect = classOverviews.reduce((sum, c) => sum + c.totalCorrect, 0) const totalActiveStudents = classOverviews.reduce((sum, c) => sum + c.activeStudents, 0) const totalStudents = classOverviews.reduce((sum, c) => sum + c.totalStudents, 0) const averageAccuracy = totalAnswered > 0 ? totalCorrect / totalAnswered : 0 const participationRate = totalStudents > 0 ? totalActiveStudents / totalStudents : 0 // 单班级视图:查询知识点薄弱度 const weakKps = effectiveClassId !== "all" ? await getClassKnowledgePointWeakness(effectiveClassId, 10) : [] const hasData = totalSessions > 0 if (!hasData) { return (

{t("teacher.title")}

{t("teacher.description")}

) } return (

{t("teacher.title")}

{t("teacher.description")}

{/* 班级筛选器 */} {classFilterData.length > 0 ? ( }> ) : null} {/* 统计卡片 */} {/* 班级对比表(仅在"全部班级"视图下显示) */} {effectiveClassId === "all" && classOverviews.length > 1 ? ( ) : null} {/* 练习类型分布图 */} {typeBreakdown.length > 0 ? ( ) : null} {/* 知识点薄弱度(仅在单班级视图下显示) */} {effectiveClassId !== "all" ? ( ) : null} {/* 学生练习排名 */} {studentSummaries.length > 0 ? ( ) : null} {/* 未参与练习学生提醒 */}
) } /** * 获取多个班级的学生练习摘要(合并结果)。 * * 用于"全部班级"视图,按班级逐个查询后合并。 */ async function getClassStudentPracticeSummariesForClasses( classIds: string[], ) { const results = await Promise.all( classIds.map((classId) => getClassStudentPracticeSummaries(classId)), ) // 合并并按练习数降序排列 return results.flat().sort((a, b) => b.totalSessions - a.totalSessions) } /** * 获取多个班级中未参与练习的学生 ID 列表(合并结果)。 */ async function getInactiveStudentsForClasses( classIds: string[], ): Promise { const results = await Promise.all( classIds.map((classId) => getStudentsWithoutPractice(classId)), ) return results.flat() } export default async function TeacherPracticePage({ searchParams, }: { searchParams: Promise }): Promise { return (
{Array.from({ length: 5 }).map((_, i) => ( ))}
} >
) }