Files
NextEdu/src/app/(dashboard)/teacher/grades/entry/page.tsx
SpecialX 1a9377222c feat(app): add error/loading boundaries and update dashboard routes
- Add error.tsx and loading.tsx boundaries for admin, parent, student, teacher routes

- Add dashboard-error-fallback and dashboard-loading-skeleton components

- Add student/learning page, parent/leave routes, teacher textbook components

- Update existing app routes across auth, dashboard, and API endpoints

- Update proxy middleware and next-auth type declarations
2026-06-23 17:38:28 +08:00

82 lines
3.1 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 type { JSX } from "react"
import { getTeacherClasses } from "@/modules/classes/data-access"
import { getClassStudentsForEntry } from "@/modules/grades/data-access"
import { getSubjectOptions } from "@/modules/school/data-access"
import { BatchGradeEntry } from "@/modules/grades/components/batch-grade-entry"
import { requirePermission } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions"
import { getParam, type SearchParams } from "@/shared/lib/search-params"
import { EmptyState } from "@/shared/components/ui/empty-state"
import { ClipboardList } from "lucide-react"
export const dynamic = "force-dynamic"
export default async function BatchEntryPage({
searchParams,
}: {
searchParams: Promise<SearchParams>
}): Promise<JSX.Element> {
const ctx = await requirePermission(Permissions.GRADE_RECORD_MANAGE)
const sp = await searchParams
const defaultClassId = getParam(sp, "classId")
const defaultSubjectId = getParam(sp, "subjectId")
// P3 修复:添加 scope 校验,对 class_taught scope 限制可录入的班级
const [classes, allSubjects, students] = await Promise.all([
getTeacherClasses(),
getSubjectOptions(),
defaultClassId
? getClassStudentsForEntry(defaultClassId, ctx.dataScope)
: Promise.resolve([] as Awaited<ReturnType<typeof getClassStudentsForEntry>>),
])
// 对 class_taught scope过滤掉不在 scope 中的班级
const allowedClassIds =
ctx.dataScope.type === "class_taught" ? ctx.dataScope.classIds : null
const scopedClasses = allowedClassIds
? classes.filter((c) => allowedClassIds.includes(c.id))
: classes
const classOptions = scopedClasses.map((c) => ({ id: c.id, name: c.name }))
const subjectOptions = allSubjects.map((s) => ({ id: s.id, name: s.name }))
// 如果指定了 classId 但 scope 不允许,显示提示
if (defaultClassId && students.length === 0 && scopedClasses.length > 0) {
const classExists = scopedClasses.some((c) => c.id === defaultClassId)
if (!classExists) {
return (
<div className="h-full flex-1 flex-col space-y-8 p-8 md:flex">
<div>
<h1 className="text-2xl font-bold tracking-tight">Batch Grade Entry</h1>
<p className="text-muted-foreground">Enter grades for all students in a class at once.</p>
</div>
<EmptyState
title="无权访问该班级"
description="您没有权限为该班级录入成绩。"
icon={ClipboardList}
className="border-none shadow-none"
/>
</div>
)
}
}
return (
<div className="h-full flex-1 flex-col space-y-8 p-8 md:flex">
<div>
<h1 className="text-2xl font-bold tracking-tight">Batch Grade Entry</h1>
<p className="text-muted-foreground">Enter grades for all students in a class at once.</p>
</div>
<BatchGradeEntry
classes={classOptions}
subjects={subjectOptions}
students={students}
defaultClassId={defaultClassId}
defaultSubjectId={defaultSubjectId}
/>
</div>
)
}