import type { Metadata } from "next" import type { JSX } from "react" import { getTranslations } from "next-intl/server" import { requirePermission } from "@/shared/lib/auth-guard" import { Permissions } from "@/shared/types/permissions" import { getTeacherIdForMutations } from "@/modules/classes/data-access" import { getGradeHomeworkInsights } from "@/modules/classes/data-access" import { getGradesForStaff } from "@/modules/school/data-access" import { GradeInsightsFilters } from "@/modules/school/components/grade-insights-filters" import { EmptyState } from "@/shared/components/ui/empty-state" import { StatCard } from "@/shared/components/ui/stat-card" import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card" import { Badge } from "@/shared/components/ui/badge" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/shared/components/ui/table" import { BarChart3 } from "lucide-react" import { formatDate, formatNumber } from "@/shared/lib/utils" import { getParam, type SearchParams } from "@/shared/lib/search-params" export const dynamic = "force-dynamic" export async function generateMetadata(): Promise { const t = await getTranslations("school") return { title: `${t("grades.gradeInsights.title")} - Next_Edu`, description: t("grades.gradeInsights.description"), } } export default async function TeacherGradeInsightsPage({ searchParams }: { searchParams: Promise }): Promise { await requirePermission(Permissions.GRADE_RECORD_READ) const t = await getTranslations("school") const params = await searchParams const gradeId = getParam(params, "gradeId") const teacherId = await getTeacherIdForMutations() const grades = await getGradesForStaff(teacherId) const allowedIds = new Set(grades.map((g) => g.id)) const selected = gradeId && gradeId !== "all" && allowedIds.has(gradeId) ? gradeId : "" const insights = selected ? await getGradeHomeworkInsights({ gradeId: selected, limit: 50 }) : null const buildHref = (gId: string): string => { const p = new URLSearchParams() if (gId && gId !== "all") p.set("gradeId", gId) const qs = p.toString() return qs ? `/management/grade/insights?${qs}` : "/management/grade/insights" } if (grades.length === 0) { return (

{t("grades.gradeInsights.title")}

{t("grades.gradeInsights.description")}

) } return (

{t("grades.gradeInsights.title")}

{t("grades.gradeInsights.description")}

{/* 年级筛选:ChipNav 即时切换,无整页刷新 */} ({ id: g.id, name: g.name, schoolName: g.school.name }))} currentGradeId={selected || "all"} buildHref={buildHref} /> {!selected ? ( ) : !insights ? ( ) : insights.assignments.length === 0 ? ( ) : (
{t("grades.gradeInsights.homeworkTimeline")} {insights.assignments.length}
{/* v4-P1-11: 移动端表格水平滚动 */}
{t("grades.gradeInsights.assignment")} {t("grades.gradeInsights.status")} {t("grades.gradeInsights.created")} {t("grades.gradeInsights.targeted")} {t("grades.gradeInsights.submitted")} {t("grades.gradeInsights.graded")} {t("grades.gradeInsights.avg")} {t("grades.gradeInsights.median")} {insights.assignments.map((a) => ( {a.title} {a.status} {formatDate(a.createdAt)} {a.targetCount} {a.submittedCount} {a.gradedCount} {formatNumber(a.scoreStats.avg)} {formatNumber(a.scoreStats.median)} ))}
{t("grades.gradeInsights.classRanking")} {insights.classes.length}
{/* v4-P1-11: 移动端表格水平滚动 */}
{t("grades.gradeInsights.class")} {t("grades.gradeInsights.students")} {t("grades.gradeInsights.latestAvgCol")} {t("grades.gradeInsights.prevAvg")} {t("grades.gradeInsights.delta")} {t("grades.gradeInsights.overallAvgCol")} {insights.classes.map((c) => ( {c.class.name} {c.class.homeroom ? • {c.class.homeroom} : null} {c.studentCounts.total} {formatNumber(c.latestAvg)} {formatNumber(c.prevAvg)} {formatNumber(c.deltaAvg)} {formatNumber(c.overallScores.avg)} ))}
)}
) }