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
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import Link from "next/link"
|
||||
import { getTranslations } from "next-intl/server"
|
||||
import { getTranslations, getLocale } from "next-intl/server"
|
||||
import { Inbox, ArrowRight } from "lucide-react"
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/shared/components/ui/avatar"
|
||||
import { Avatar, AvatarFallback } from "@/shared/components/ui/avatar"
|
||||
import { Badge } from "@/shared/components/ui/badge"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import { EmptyState } from "@/shared/components/ui/empty-state"
|
||||
@@ -32,6 +32,7 @@ export async function RecentSubmissions({
|
||||
emptyDescription,
|
||||
}: RecentSubmissionsProps) {
|
||||
const t = await getTranslations("dashboard")
|
||||
const locale = await getLocale()
|
||||
const hasSubmissions = submissions.length > 0
|
||||
|
||||
return (
|
||||
@@ -73,7 +74,6 @@ export async function RecentSubmissions({
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="h-8 w-8 border">
|
||||
<AvatarImage src={undefined} alt={item.studentName} />
|
||||
<AvatarFallback className="bg-primary/10 text-primary text-xs">
|
||||
{item.studentName.charAt(0)}
|
||||
</AvatarFallback>
|
||||
@@ -93,7 +93,7 @@ export async function RecentSubmissions({
|
||||
<TableCell>
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{item.submittedAt ? formatDate(item.submittedAt) : "-"}
|
||||
{item.submittedAt ? formatDate(item.submittedAt, locale) : "-"}
|
||||
</span>
|
||||
{item.isLate && (
|
||||
<Badge variant="destructive" className="w-fit text-[10px] h-4 px-1.5 font-normal">
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import { DashboardGreetingHeader } from "../dashboard-greeting-header"
|
||||
import { TeacherQuickActions } from "./teacher-quick-actions"
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ export function TeacherGradeTrends({ trends }: { trends: TeacherGradeTrendItem[]
|
||||
.reverse()
|
||||
.slice(0, 3)
|
||||
.map((item, i) => (
|
||||
<div key={i} className="flex flex-col gap-1 rounded-lg border p-3 bg-card/50">
|
||||
<div key={item.fullTitle || `item-${i}`} className="flex flex-col gap-1 rounded-lg border p-3 bg-card/50">
|
||||
<div className="text-xs text-muted-foreground truncate" title={item.fullTitle}>
|
||||
{item.fullTitle}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { PlusCircle, CheckSquare, Users } from "lucide-react"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { getTranslations } from "next-intl/server"
|
||||
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
|
||||
export function TeacherQuickActions() {
|
||||
const t = useTranslations("dashboard")
|
||||
export async function TeacherQuickActions() {
|
||||
const t = await getTranslations("dashboard")
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
|
||||
@@ -21,14 +21,14 @@ export async function TeacherSchedule({ items }: { items: TeacherTodayScheduleIt
|
||||
const t = await getTranslations("dashboard")
|
||||
const hasSchedule = items.length > 0
|
||||
|
||||
const getStatus = (start: string, end: string) => {
|
||||
const getStatus = (start: string, end: string): "live" | "upcoming" | "past" => {
|
||||
const now = new Date()
|
||||
const currentTime = now.getHours() * 60 + now.getMinutes()
|
||||
|
||||
const [startH, startM] = start.split(":").map(Number)
|
||||
const [endH, endM] = end.split(":").map(Number)
|
||||
const startTime = (startH ?? 0) * 60 + (startM ?? 0)
|
||||
const endTime = (endH ?? 0) * 60 + (endM ?? 0)
|
||||
const startTime = (Number.isFinite(startH) ? startH : 0) * 60 + (Number.isFinite(startM) ? startM : 0)
|
||||
const endTime = (Number.isFinite(endH) ? endH : 0) * 60 + (Number.isFinite(endM) ? endM : 0)
|
||||
|
||||
if (currentTime >= startTime && currentTime <= endTime) return "live"
|
||||
if (currentTime < startTime) return "upcoming"
|
||||
|
||||
@@ -7,7 +7,6 @@ interface TeacherStatsProps {
|
||||
activeAssignmentsCount: number
|
||||
averageScore: number
|
||||
submissionRate: number
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
export async function TeacherStats({
|
||||
@@ -15,7 +14,6 @@ export async function TeacherStats({
|
||||
activeAssignmentsCount,
|
||||
averageScore,
|
||||
submissionRate,
|
||||
isLoading = false,
|
||||
}: TeacherStatsProps) {
|
||||
const t = await getTranslations("dashboard")
|
||||
|
||||
@@ -29,7 +27,6 @@ export async function TeacherStats({
|
||||
href="/teacher/homework/submissions?status=submitted"
|
||||
highlight={toGradeCount > 0}
|
||||
color="text-amber-500"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<StatCard
|
||||
title={t("stats.activeAssignments")}
|
||||
@@ -38,7 +35,6 @@ export async function TeacherStats({
|
||||
icon={PenTool}
|
||||
href="/teacher/homework/assignments?status=published"
|
||||
color="text-blue-500"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<StatCard
|
||||
title={t("stats.averageScore")}
|
||||
@@ -47,7 +43,6 @@ export async function TeacherStats({
|
||||
icon={TrendingUp}
|
||||
href="#grade-trends"
|
||||
color="text-emerald-500"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<StatCard
|
||||
title={t("stats.submissionRate")}
|
||||
@@ -56,7 +51,6 @@ export async function TeacherStats({
|
||||
icon={BarChart}
|
||||
href="#grade-trends"
|
||||
color="text-purple-500"
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -49,13 +49,17 @@ export async function TeacherTodoCard({ items }: TeacherTodoCardProps) {
|
||||
<div className="space-y-1">
|
||||
{items
|
||||
.filter((item) => item.count > 0)
|
||||
.sort((a, b) => (a.variant === "urgent" ? -1 : 1) - (b.variant === "urgent" ? -1 : 1))
|
||||
.sort((a, b) => {
|
||||
if (a.variant === "urgent" && b.variant !== "urgent") return -1
|
||||
if (a.variant !== "urgent" && b.variant === "urgent") return 1
|
||||
return 0
|
||||
})
|
||||
.map((item, idx) => {
|
||||
const style = VARIANT_STYLES[item.variant]
|
||||
const Icon = style.icon
|
||||
return (
|
||||
<Link
|
||||
key={idx}
|
||||
key={item.href || item.label || `item-${idx}`}
|
||||
href={item.href}
|
||||
className="group flex items-center justify-between rounded-md border border-transparent px-3 py-2 hover:bg-muted/50 hover:border-border transition-colors"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user