feat(classes): optimize teacher dashboard ui and implement grade management

This commit is contained in:
SpecialX
2026-01-14 13:59:11 +08:00
parent ade8d4346c
commit 9bfc621d3f
104 changed files with 12793 additions and 2309 deletions

View File

@@ -4,14 +4,16 @@ import { redirect } from "next/navigation"
import { auth } from "@/auth"
import { getStudentClasses, getStudentSchedule } from "@/modules/classes/data-access"
import { StudentGradesCard } from "@/modules/dashboard/components/student-dashboard/student-grades-card"
import { StudentRankingCard } from "@/modules/dashboard/components/student-dashboard/student-ranking-card"
import { StudentStatsGrid } from "@/modules/dashboard/components/student-dashboard/student-stats-grid"
import { StudentTodayScheduleCard } from "@/modules/dashboard/components/student-dashboard/student-today-schedule-card"
import { StudentUpcomingAssignmentsCard } from "@/modules/dashboard/components/student-dashboard/student-upcoming-assignments-card"
import { getStudentDashboardGrades, getStudentHomeworkAssignments } from "@/modules/homework/data-access"
import { getUserProfile } from "@/modules/users/data-access"
import { Badge } from "@/shared/components/ui/badge"
import { Button } from "@/shared/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/shared/components/ui/card"
import { Separator } from "@/shared/components/ui/separator"
import { User, Mail, Phone, MapPin, Calendar, Clock, Shield } from "lucide-react"
export const dynamic = "force-dynamic"
@@ -20,17 +22,31 @@ const toWeekday = (d: Date): 1 | 2 | 3 | 4 | 5 | 6 | 7 => {
return (day === 0 ? 7 : day) as 1 | 2 | 3 | 4 | 5 | 6 | 7
}
const formatDate = (date: Date | null) => {
if (!date) return "-"
return new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric",
}).format(date)
}
export default async function ProfilePage() {
const session = await auth()
if (!session?.user) redirect("/login")
const name = session.user.name ?? "User"
const email = session.user.email ?? "-"
const role = String(session.user.role ?? "teacher")
const userId = String(session.user.id ?? "").trim()
const userProfile = await getUserProfile(userId)
if (!userProfile) {
redirect("/login")
}
const role = userProfile.role || "student"
const isStudent = role === "student"
const studentData =
role === "student" && userId
isStudent
? await (async () => {
const [classes, schedule, assignmentsAll, grades] = await Promise.all([
getStudentClasses(userId),
@@ -96,36 +112,104 @@ export default async function ProfilePage() {
<div className="flex flex-col justify-between gap-4 md:flex-row md:items-center">
<div className="space-y-1">
<h1 className="text-3xl font-bold tracking-tight">Profile</h1>
<div className="text-sm text-muted-foreground">Your account information.</div>
<div className="text-sm text-muted-foreground">Manage your personal and account information.</div>
</div>
<div className="flex items-center gap-2">
<Button asChild variant="outline">
<Link href="/settings">Open settings</Link>
<Link href="/settings">Edit Profile</Link>
</Button>
</div>
</div>
<Card>
<CardHeader>
<CardTitle>Account</CardTitle>
<CardDescription>Signed-in user details from session.</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex flex-wrap items-center gap-2">
<div className="text-sm font-medium">{name}</div>
<Badge variant="secondary" className="capitalize">
{role}
</Badge>
</div>
<div className="text-sm text-muted-foreground">{email}</div>
</CardContent>
</Card>
<div className="grid gap-6 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<User className="h-5 w-5" />
Personal Information
</CardTitle>
<CardDescription>Basic personal details.</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="space-y-1">
<div className="text-sm font-medium text-muted-foreground">Full Name</div>
<div className="text-sm font-medium">{userProfile.name ?? "-"}</div>
</div>
<div className="space-y-1">
<div className="text-sm font-medium text-muted-foreground">Gender</div>
<div className="text-sm capitalize">{userProfile.gender ?? "-"}</div>
</div>
<div className="space-y-1">
<div className="text-sm font-medium text-muted-foreground">Age</div>
<div className="text-sm">{userProfile.age ?? "-"}</div>
</div>
<div className="space-y-1">
<div className="text-sm font-medium text-muted-foreground">Phone</div>
<div className="flex items-center gap-2 text-sm">
{userProfile.phone ? <Phone className="h-3 w-3 text-muted-foreground" /> : null}
{userProfile.phone ?? "-"}
</div>
</div>
<div className="col-span-1 sm:col-span-2 space-y-1">
<div className="text-sm font-medium text-muted-foreground">Address</div>
<div className="flex items-center gap-2 text-sm">
{userProfile.address ? <MapPin className="h-3 w-3 text-muted-foreground" /> : null}
{userProfile.address ?? "-"}
</div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Shield className="h-5 w-5" />
Account Information
</CardTitle>
<CardDescription>System account details.</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="col-span-1 sm:col-span-2 space-y-1">
<div className="text-sm font-medium text-muted-foreground">Email</div>
<div className="flex items-center gap-2 text-sm">
<Mail className="h-3 w-3 text-muted-foreground" />
{userProfile.email}
</div>
</div>
<div className="space-y-1">
<div className="text-sm font-medium text-muted-foreground">Role</div>
<Badge variant="secondary" className="capitalize">
{userProfile.role}
</Badge>
</div>
<div className="space-y-1">
<div className="text-sm font-medium text-muted-foreground">Member Since</div>
<div className="flex items-center gap-2 text-sm">
<Calendar className="h-3 w-3 text-muted-foreground" />
{formatDate(userProfile.createdAt)}
</div>
</div>
<div className="space-y-1">
<div className="text-sm font-medium text-muted-foreground">Onboarded At</div>
<div className="flex items-center gap-2 text-sm">
<Clock className="h-3 w-3 text-muted-foreground" />
{formatDate(userProfile.onboardedAt)}
</div>
</div>
</div>
</CardContent>
</Card>
</div>
{studentData ? (
<div className="space-y-6">
<Separator />
<div className="space-y-1">
<h2 className="text-xl font-semibold tracking-tight">Student</h2>
<div className="text-sm text-muted-foreground">Your learning overview.</div>
<h2 className="text-xl font-semibold tracking-tight">Student Overview</h2>
<div className="text-sm text-muted-foreground">Your academic performance and schedule.</div>
</div>
<StudentStatsGrid
@@ -133,16 +217,17 @@ export default async function ProfilePage() {
dueSoonCount={studentData.dueSoonCount}
overdueCount={studentData.overdueCount}
gradedCount={studentData.gradedCount}
ranking={studentData.grades.ranking}
/>
<div className="grid gap-4 md:grid-cols-2">
<StudentGradesCard grades={studentData.grades} />
<StudentRankingCard ranking={studentData.grades.ranking} />
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
<StudentTodayScheduleCard items={studentData.todayScheduleItems} />
<StudentUpcomingAssignmentsCard upcomingAssignments={studentData.upcomingAssignments} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-6">
<StudentUpcomingAssignmentsCard upcomingAssignments={studentData.upcomingAssignments} />
<StudentGradesCard grades={studentData.grades} />
</div>
<div className="space-y-6">
<StudentTodayScheduleCard items={studentData.todayScheduleItems} />
</div>
</div>
</div>
) : null}