Security: Add admin/layout.tsx auth guard; Add requirePermission() to 12 admin pages Dashboard: Fix StudentStatsGrid rendering; Fix teacher greeting; Add loading/error boundaries; Fix col-span; Add metadata Announcements: Fix audience filtering; Add user detail page; Trigger notifications on publish; Pass classes data; Add loading.tsx Messages: Implement soft delete; Add unread badge with polling; Add notification dropdown polling; Add keyword search; Add quiet hours DND Management: Add loading/error for 9 admin routes; Fix admin-classes-view to use Select for school/grade Profile/Settings: Add loading/error; Fix parent role routing; Create ParentSettingsView; Integrate AiProviderSettingsCard; Add Tab URL persistence; Add logout confirm; Add avatar; Fix Progress arbitrary class Schema: Add senderDeletedAt/receiverDeletedAt to messages; Add quietHours to notificationPreferences; Add uniqueIndex import Docs: Update architecture docs 004/005
103 lines
3.1 KiB
TypeScript
103 lines
3.1 KiB
TypeScript
import { StudentDashboard } from "@/modules/dashboard/components/student-dashboard/student-dashboard-view"
|
||
import { getStudentClasses, getStudentSchedule } from "@/modules/classes/data-access"
|
||
import { getStudentDashboardGrades, getStudentHomeworkAssignments } from "@/modules/homework/data-access"
|
||
import { getCurrentStudentUser } from "@/modules/users/data-access"
|
||
import { EmptyState } from "@/shared/components/ui/empty-state"
|
||
import { UserX } from "lucide-react"
|
||
import type { StudentHomeworkProgressStatus } from "@/modules/homework/types"
|
||
|
||
export const dynamic = "force-dynamic"
|
||
|
||
export const metadata = { title: "Dashboard - Next_Edu" }
|
||
|
||
const toWeekday = (d: Date): 1 | 2 | 3 | 4 | 5 | 6 | 7 => {
|
||
// getDay() 返回 0(周日)-6(周六),转换为 1-7(周一为 1)
|
||
const WEEKDAY_MAP = [7, 1, 2, 3, 4, 5, 6] as const
|
||
const day = d.getDay()
|
||
if (day < 0 || day > 6) {
|
||
throw new Error(`Invalid day from getDay(): ${day}`)
|
||
}
|
||
return WEEKDAY_MAP[day]
|
||
}
|
||
|
||
export default async function StudentDashboardPage() {
|
||
const student = await getCurrentStudentUser()
|
||
if (!student) {
|
||
return (
|
||
<EmptyState
|
||
title="No user found"
|
||
description="Create a student user to see dashboard."
|
||
icon={UserX}
|
||
className="border-none shadow-none h-auto"
|
||
/>
|
||
)
|
||
}
|
||
|
||
const [classes, schedule, assignments, grades] = await Promise.all([
|
||
getStudentClasses(student.id),
|
||
getStudentSchedule(student.id),
|
||
getStudentHomeworkAssignments(student.id),
|
||
getStudentDashboardGrades(student.id),
|
||
])
|
||
|
||
const now = new Date()
|
||
const in7Days = new Date(now)
|
||
in7Days.setDate(in7Days.getDate() + 7)
|
||
|
||
// 单次遍历统计,避免重复 filter(PERF-04)
|
||
let dueSoonCount = 0
|
||
let overdueCount = 0
|
||
let gradedCount = 0
|
||
for (const a of assignments) {
|
||
const status: StudentHomeworkProgressStatus = a.progressStatus
|
||
if (status === "graded") {
|
||
gradedCount++
|
||
continue
|
||
}
|
||
if (!a.dueAt) continue
|
||
const due = new Date(a.dueAt)
|
||
if (due >= now && due <= in7Days) {
|
||
dueSoonCount++
|
||
} else if (due < now) {
|
||
overdueCount++
|
||
}
|
||
}
|
||
|
||
const todayWeekday = toWeekday(now)
|
||
const todayScheduleItems = schedule
|
||
.filter((s) => s.weekday === todayWeekday)
|
||
.map((s) => ({
|
||
id: s.id,
|
||
classId: s.classId,
|
||
className: s.className,
|
||
course: s.course,
|
||
startTime: s.startTime,
|
||
endTime: s.endTime,
|
||
location: s.location ?? null,
|
||
}))
|
||
.sort((a, b) => a.startTime.localeCompare(b.startTime))
|
||
|
||
const upcomingAssignments = [...assignments]
|
||
.sort((a, b) => {
|
||
const aDue = a.dueAt ? new Date(a.dueAt).getTime() : Number.POSITIVE_INFINITY
|
||
const bDue = b.dueAt ? new Date(b.dueAt).getTime() : Number.POSITIVE_INFINITY
|
||
return aDue - bDue
|
||
})
|
||
.slice(0, 6)
|
||
|
||
return (
|
||
<div className="space-y-8">
|
||
<StudentDashboard
|
||
studentName={student.name}
|
||
enrolledClassCount={classes.length}
|
||
dueSoonCount={dueSoonCount}
|
||
overdueCount={overdueCount}
|
||
gradedCount={gradedCount}
|
||
todayScheduleItems={todayScheduleItems}
|
||
upcomingAssignments={upcomingAssignments}
|
||
grades={grades}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|