Files
NextEdu/src/app/(dashboard)/parent/grades/page.tsx
SpecialX 21142f9b99 feat(app): add error/loading boundaries across all dashboard routes and new routes
- Add error.tsx and loading.tsx boundaries for admin, parent, student, teacher routes

- Add admin announcements edit, audit-logs overview, curriculum-map, invitation-codes, permissions, questions, roles routes

- Add admin elective detail and components, files, course-plans, users, scheduling boundaries

- Add messages group-compose route

- Add parent course-plans, elective, grades report-card, practice routes

- Add student course-plans, elective detail, error-book dialogs, grades report-card, learning study-path, leave, schedule boundaries

- Add teacher attendance report, classes boundaries, course-plans boundaries, elective, exams analytics/edit-rich/all/create/new, grades report-card, homework boundaries, leave, lesson-plans calendar

- Add auth loading, onboarding loading, api cron
2026-07-03 10:26:25 +08:00

91 lines
3.7 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 { getStudentGrowthArchive } from "@/modules/grades/data-access-analytics"
import { StudentGradeSummary } from "@/modules/grades/components/student-grade-summary"
import { GradeTrendCard } from "@/modules/grades/components/grade-trend-card"
import { GrowthArchiveChart } from "@/modules/grades/components/growth-archive-chart"
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, StudentGrowthArchiveResult } 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
growthArchive: StudentGrowthArchiveResult | 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, growthArchive] = await Promise.all([
getStudentGradeSummary(id, ctx.dataScope),
// v3-P2-8家长页面补齐趋势图复用班级平均对比线
getClassAverageTrend(id, undefined, undefined, ctx.dataScope),
// P3-4子女纵向成长档案
getStudentGrowthArchive(id, ctx.dataScope),
])
return { summary, classAverageTrend, growthArchive, studentId: id }
}),
)
// P1-8 修复:用循环 + 类型守卫替代 `as` 断言
const validItems: ChildGradeItem[] = []
for (const r of results) {
if (r.status !== "fulfilled") continue
const { summary, classAverageTrend, growthArchive, studentId } = r.value
if (summary === null) continue
validItems.push({ studentId, summary, classAverageTrend, growthArchive })
}
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, growthArchive }) => (
<>
<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} />
)}
{/* P3-4子女纵向成长档案 */}
<GrowthArchiveChart data={growthArchive} />
<StudentGradeSummary summary={summary} />
</>
)}
/>
)
}