Files
NextEdu/src/app/(dashboard)/parent/grades/page.tsx
SpecialX 37d2688a28 feat(app): add lesson-plans, practice, and grade dashboard routes
- Add admin/lesson-plans, parent/lesson-plans, student/lesson-plans routes

- Add student/practice and teacher/practice routes for adaptive practice

- Add management/grade/dashboard and management/grade/practice routes

- Add teacher/lesson-plans error and loading boundaries

- Update existing admin, parent, student, teacher pages with new features

- Update globals.css and proxy middleware
2026-06-24 12:03:47 +08:00

91 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { requirePermission } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions"
import { getStudentGradeSummary } from "@/modules/grades/data-access"
import { getClassAverageTrend } from "@/modules/grades/data-access-ranking"
import { StudentGradeSummary } from "@/modules/grades/components/student-grade-summary"
import { GradeTrendCard } from "@/modules/grades/components/grade-trend-card"
import {
ParentChildrenDataPage,
ParentNoChildrenPage,
} from "@/modules/parent/components/parent-children-data-page"
import { ParentExportButton } from "@/modules/parent/components/parent-export-button"
import { GraduationCap } from "lucide-react"
import type { ClassAverageTrendResult } from "@/modules/grades/types"
import { getTranslations } from "next-intl/server"
export const dynamic = "force-dynamic"
interface ChildGradeItem {
studentId: string
summary: NonNullable<Awaited<ReturnType<typeof getStudentGradeSummary>>>
classAverageTrend: ClassAverageTrendResult | null
}
export default async function ParentGradesPage() {
const ctx = await requirePermission(Permissions.GRADE_RECORD_READ)
const t = await getTranslations("grades")
if (ctx.dataScope.type !== "children" || ctx.dataScope.childrenIds.length === 0) {
return (
<ParentNoChildrenPage
title={t("parent.title")}
description={t("parent.description")}
icon={GraduationCap}
emptyTitle={t("parent.noChildren")}
emptyDescription={t("parent.noChildrenDesc")}
/>
)
}
// 使用 allSettled 容错:单个子女查询失败不影响其他子女展示
const results = await Promise.allSettled(
ctx.dataScope.childrenIds.map(async (id) => {
const [summary, classAverageTrend] = await Promise.all([
getStudentGradeSummary(id, ctx.dataScope),
// v3-P2-8家长页面补齐趋势图复用班级平均对比线
getClassAverageTrend(id, undefined, undefined, ctx.dataScope),
])
return { summary, classAverageTrend, studentId: id }
}),
)
const validItems: ChildGradeItem[] = results
.filter(
(
r,
): r is PromiseFulfilledResult<{
summary: Awaited<ReturnType<typeof getStudentGradeSummary>>
classAverageTrend: ClassAverageTrendResult | null
studentId: string
}> => r.status === "fulfilled" && r.value.summary !== null,
)
.map((r) => ({
studentId: r.value.studentId,
summary: r.value.summary as NonNullable<typeof r.value.summary>,
classAverageTrend: r.value.classAverageTrend,
}))
return (
<ParentChildrenDataPage
title={t("parent.title")}
description={t("parent.description")}
icon={GraduationCap}
noRecordsTitle={t("parent.noGrades")}
noRecordsDescription={t("parent.noGradesDesc")}
items={validItems}
renderItem={({ studentId, summary, classAverageTrend }) => (
<>
<div className="flex items-center justify-between border-b pb-2">
<h3 className="text-lg font-semibold">{summary.studentName}</h3>
{/* v4-P1-12: 接入 exportGradesAction支持按 studentId 导出 */}
<ParentExportButton studentId={studentId} studentName={summary.studentName} />
</div>
{summary.records.length > 0 && (
<GradeTrendCard summary={summary} classAverageData={classAverageTrend} />
)}
<StudentGradeSummary summary={summary} />
</>
)}
/>
)
}