feat(app): add lesson-plans, practice, and grade dashboard routes
- Add admin/lesson-plans, parent/lesson-plans, student/lesson-plans routes - Add student/practice and teacher/practice routes for adaptive practice - Add management/grade/dashboard and management/grade/practice routes - Add teacher/lesson-plans error and loading boundaries - Update existing admin, parent, student, teacher pages with new features - Update globals.css and proxy middleware
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
School,
|
||||
User,
|
||||
} from "lucide-react"
|
||||
import { getTranslations } from "next-intl/server"
|
||||
|
||||
import { getStudentClassById, getStudentSchedule } from "@/modules/classes/data-access"
|
||||
import { getCurrentStudentUser } from "@/modules/users/data-access"
|
||||
@@ -20,14 +21,14 @@ import { EmptyState } from "@/shared/components/ui/empty-state"
|
||||
|
||||
export const dynamic = "force-dynamic"
|
||||
|
||||
const WEEKDAYS: Record<number, string> = {
|
||||
1: "Mon",
|
||||
2: "Tue",
|
||||
3: "Wed",
|
||||
4: "Thu",
|
||||
5: "Fri",
|
||||
6: "Sat",
|
||||
7: "Sun",
|
||||
const WEEKDAY_KEYS: Record<number, string> = {
|
||||
1: "mon",
|
||||
2: "tue",
|
||||
3: "wed",
|
||||
4: "thu",
|
||||
5: "fri",
|
||||
6: "sat",
|
||||
7: "sun",
|
||||
}
|
||||
|
||||
export default async function StudentClassDetailPage({
|
||||
@@ -39,6 +40,8 @@ export default async function StudentClassDetailPage({
|
||||
const student = await getCurrentStudentUser()
|
||||
if (!student) return notFound()
|
||||
|
||||
const t = await getTranslations("student")
|
||||
|
||||
const [classInfo, schedule] = await Promise.all([
|
||||
getStudentClassById(student.id, classId),
|
||||
getStudentSchedule(student.id),
|
||||
@@ -58,14 +61,14 @@ export default async function StudentClassDetailPage({
|
||||
<Button asChild variant="ghost" size="sm" className="-ml-2 mb-1">
|
||||
<Link href="/student/learning/courses">
|
||||
<ChevronLeft className="mr-1 h-4 w-4" />
|
||||
Back to Courses
|
||||
{t("classDetail.backToCourses")}
|
||||
</Link>
|
||||
</Button>
|
||||
<h2 className="text-2xl font-bold tracking-tight">{classInfo.name}</h2>
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
|
||||
<span className="flex items-center gap-1">
|
||||
<BookOpen className="h-4 w-4" />
|
||||
Grade {classInfo.grade}
|
||||
{t("classDetail.grade", { grade: classInfo.grade })}
|
||||
</span>
|
||||
{classInfo.homeroom && (
|
||||
<>
|
||||
@@ -78,24 +81,24 @@ export default async function StudentClassDetailPage({
|
||||
<span aria-hidden="true">•</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Building2 className="h-4 w-4" />
|
||||
Room {classInfo.room}
|
||||
{t("classDetail.room", { room: classInfo.room })}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<Badge variant="secondary">Active</Badge>
|
||||
<Badge variant="secondary">{t("classDetail.active")}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<Link href={`/student/schedule?classId=${encodeURIComponent(classInfo.id)}`}>
|
||||
<CalendarDays className="mr-2 h-4 w-4" />
|
||||
Full Schedule
|
||||
{t("classDetail.fullSchedule")}
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild size="sm">
|
||||
<Link href="/student/learning/assignments">
|
||||
<PenTool className="mr-2 h-4 w-4" />
|
||||
Assignments
|
||||
{t("classDetail.assignments")}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -107,7 +110,7 @@ export default async function StudentClassDetailPage({
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-sm font-medium">
|
||||
<User className="h-4 w-4" />
|
||||
Teacher
|
||||
{t("classDetail.teacher")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 text-sm">
|
||||
@@ -117,7 +120,7 @@ export default async function StudentClassDetailPage({
|
||||
<span className="font-medium">{classInfo.teacherName}</span>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted-foreground">No teacher assigned.</p>
|
||||
<p className="text-muted-foreground">{t("classDetail.noTeacher")}</p>
|
||||
)}
|
||||
{classInfo.teacherEmail && (
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -138,7 +141,7 @@ export default async function StudentClassDetailPage({
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-sm font-medium">
|
||||
<School className="h-4 w-4" />
|
||||
School
|
||||
{t("classDetail.school")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 text-sm">
|
||||
@@ -148,12 +151,12 @@ export default async function StudentClassDetailPage({
|
||||
<span className="font-medium">{classInfo.schoolName}</span>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted-foreground">School info not available.</p>
|
||||
<p className="text-muted-foreground">{t("classDetail.schoolNotAvailable")}</p>
|
||||
)}
|
||||
{classInfo.grade && (
|
||||
<div className="flex items-center gap-2">
|
||||
<BookOpen className="h-4 w-4 text-muted-foreground" />
|
||||
<span>Grade {classInfo.grade}</span>
|
||||
<span>{t("classDetail.grade", { grade: classInfo.grade })}</span>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
@@ -164,22 +167,22 @@ export default async function StudentClassDetailPage({
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-sm font-medium">
|
||||
<Building2 className="h-4 w-4" />
|
||||
Classroom
|
||||
{t("classDetail.classroom")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 text-sm">
|
||||
{classInfo.room ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Building2 className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="font-medium">Room {classInfo.room}</span>
|
||||
<span className="font-medium">{t("classDetail.room", { room: classInfo.room })}</span>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted-foreground">Room not assigned.</p>
|
||||
<p className="text-muted-foreground">{t("classDetail.roomNotAssigned")}</p>
|
||||
)}
|
||||
{classInfo.homeroom && (
|
||||
<div className="flex items-center gap-2">
|
||||
<User className="h-4 w-4 text-muted-foreground" />
|
||||
<span>Homeroom: {classInfo.homeroom}</span>
|
||||
<span>{t("classDetail.homeroom", { homeroom: classInfo.homeroom })}</span>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
@@ -191,15 +194,15 @@ export default async function StudentClassDetailPage({
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<CalendarDays className="h-5 w-5" />
|
||||
Class Schedule
|
||||
{t("classDetail.classSchedule")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{classSchedule.length === 0 ? (
|
||||
<EmptyState
|
||||
icon={CalendarDays}
|
||||
title="No schedule"
|
||||
description="No timetable entries found for this class."
|
||||
title={t("classDetail.noSchedule")}
|
||||
description={t("classDetail.noScheduleDesc")}
|
||||
className="border-none shadow-none"
|
||||
/>
|
||||
) : (
|
||||
@@ -211,7 +214,7 @@ export default async function StudentClassDetailPage({
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="outline" className="w-12 justify-center">
|
||||
{WEEKDAYS[s.weekday]}
|
||||
{t(`weekdays.${WEEKDAY_KEYS[s.weekday]}`)}
|
||||
</Badge>
|
||||
<div>
|
||||
<p className="font-medium">{s.course}</p>
|
||||
|
||||
Reference in New Issue
Block a user