feat: enhance textbook reader with anchor text support and improve knowledge point management

This commit is contained in:
SpecialX
2026-01-16 10:22:16 +08:00
parent 9bfc621d3f
commit bb4555f611
44 changed files with 6284 additions and 2090 deletions

View File

@@ -1,7 +1,7 @@
import { Suspense } from "react"
import { User } from "lucide-react"
import { getClassStudents, getTeacherClasses } from "@/modules/classes/data-access"
import { getClassStudents, getTeacherClasses, getStudentsSubjectScores } from "@/modules/classes/data-access"
import { StudentsFilters } from "@/modules/classes/components/students-filters"
import { StudentsTable } from "@/modules/classes/components/students-table"
import { EmptyState } from "@/shared/components/ui/empty-state"
@@ -16,19 +16,34 @@ const getParam = (params: SearchParams, key: string) => {
return Array.isArray(v) ? v[0] : v
}
async function StudentsResults({ searchParams }: { searchParams: Promise<SearchParams> }) {
async function StudentsResults({ searchParams, defaultClassId }: { searchParams: Promise<SearchParams>, defaultClassId?: string }) {
const params = await searchParams
const q = getParam(params, "q") || undefined
const classId = getParam(params, "classId")
const status = getParam(params, "status")
// If classId is explicit in URL, use it (unless "all"). If not, use defaultClassId.
// If user explicitly selects "all", classId will be "all".
// However, the requirement is "Default to showing the first class".
// If classId param is missing, we use defaultClassId.
const targetClassId = classId ? (classId !== "all" ? classId : undefined) : defaultClassId
const filteredStudents = await getClassStudents({
q,
classId: classId && classId !== "all" ? classId : undefined,
classId: targetClassId,
status: status && status !== "all" ? status : undefined,
})
// Fetch subject scores for all filtered students
if (filteredStudents.length > 0) {
const studentIds = filteredStudents.map(s => s.id)
const scores = await getStudentsSubjectScores(studentIds)
for (const student of filteredStudents) {
student.subjectScores = scores.get(student.id)
}
}
const hasFilters = Boolean(q || (classId && classId !== "all") || (status && status !== "all"))
if (filteredStudents.length === 0) {
@@ -67,25 +82,20 @@ function StudentsResultsFallback() {
export default async function StudentsPage({ searchParams }: { searchParams: Promise<SearchParams> }) {
const classes = await getTeacherClasses()
const params = await searchParams
// Logic to determine default class (first one available)
const defaultClassId = classes.length > 0 ? classes[0].id : undefined
return (
<div className="flex h-full flex-col space-y-8 p-8">
<div className="flex flex-col justify-between space-y-4 md:flex-row md:items-center md:space-y-0">
<div>
<h2 className="text-2xl font-bold tracking-tight">Students</h2>
<p className="text-muted-foreground">
Manage student list.
</p>
</div>
</div>
<div className="flex h-full flex-col space-y-4 p-8">
<div className="space-y-4">
<Suspense fallback={<div className="h-10 w-full animate-pulse rounded-md bg-muted" />}>
<StudentsFilters classes={classes} />
<StudentsFilters classes={classes} defaultClassId={defaultClassId} />
</Suspense>
<Suspense fallback={<StudentsResultsFallback />}>
<StudentsResults searchParams={searchParams} />
<StudentsResults searchParams={searchParams} defaultClassId={defaultClassId} />
</Suspense>
</div>
</div>