Files
NextEdu/src/app/(dashboard)/student/learning/textbooks/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

87 lines
2.9 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 { BookOpen, UserX } from "lucide-react"
import { getTranslations } from "next-intl/server"
import { getTextbooksWithScope } from "@/modules/textbooks/data-access"
import { TextbookCard } from "@/modules/textbooks/components/textbook-card"
import { TextbookFilters } from "@/modules/textbooks/components/textbook-filters"
import { getCurrentStudentUser } from "@/modules/users/data-access"
import { getGradeNameById } from "@/modules/school/data-access"
import { EmptyState } from "@/shared/components/ui/empty-state"
import { getParam, type SearchParams } from "@/shared/lib/search-params"
export const dynamic = "force-dynamic"
export default async function StudentTextbooksPage({
searchParams,
}: {
searchParams: Promise<SearchParams>
}) {
const t = await getTranslations("textbooks")
const [student, sp] = await Promise.all([getCurrentStudentUser(), searchParams])
if (!student) {
return (
<div className="space-y-8">
<EmptyState
title={t("student.noUser")}
description={t("student.noUserDesc")}
icon={UserX}
/>
</div>
)
}
const q = getParam(sp, "q")
const subject = getParam(sp, "subject")
const grade = getParam(sp, "grade")
// P1-1 数据范围过滤:学生端强制按学生所在年级过滤
// student.gradeId 是 grades 表 id需通过 getGradeNameById 解析为年级名称(如 "Grade 7"
// 才能与 textbooks.grade 字符串字段匹配
const studentGradeName = student.gradeId ? await getGradeNameById(student.gradeId) : null
const textbooks = await getTextbooksWithScope(q, subject, grade, {
grade: studentGradeName ?? undefined,
})
const hasFilters = Boolean(q || (subject && subject !== "all") || (grade && grade !== "all"))
return (
<div className="space-y-8">
<div>
<h2 className="text-2xl font-bold tracking-tight">{t("student.list.title")}</h2>
<p className="text-muted-foreground">{t("student.list.subtitle")}</p>
</div>
<TextbookFilters />
{textbooks.length === 0 ? (
<EmptyState
icon={BookOpen}
title={hasFilters ? t("student.list.empty.withFilters") : t("student.list.empty.withoutFilters")}
description={
hasFilters
? t("student.list.empty.withFiltersDesc")
: t("student.list.empty.withoutFiltersDesc")
}
action={
hasFilters
? { label: t("list.clearFilters"), href: "/student/learning/textbooks" }
: undefined
}
className="bg-card"
/>
) : (
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{textbooks.map((textbook) => (
<TextbookCard
key={textbook.id}
textbook={textbook}
hrefBase="/student/learning/textbooks"
hideActions
/>
))}
</div>
)}
</div>
)
}