feat(dashboard): optimize teacher dashboard ui and layout

- Refactor layout: move Needs Grading to main column, Homework to sidebar
- Enhance TeacherStats: replace static counts with actionable metrics (Needs Grading, Active Assignments, Avg Score, Submission Rate)
- Update RecentSubmissions: table view with quick grade actions and late status
- Update TeacherSchedule: vertical timeline view with scroll hints
- Update TeacherHomeworkCard: compact list view
- Integrate Recharts: add TeacherGradeTrends chart and shared chart component
- Update documentation
This commit is contained in:
SpecialX
2026-01-12 11:38:27 +08:00
parent 8577280ab2
commit ade8d4346c
17 changed files with 1383 additions and 234 deletions

View File

@@ -1,67 +1,114 @@
import Link from "next/link";
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 { Badge } from "@/shared/components/ui/badge";
import { Button } from "@/shared/components/ui/button";
import { EmptyState } from "@/shared/components/ui/empty-state";
import { Inbox } from "lucide-react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/shared/components/ui/table";
import { formatDate } from "@/shared/lib/utils";
import type { HomeworkSubmissionListItem } from "@/modules/homework/types";
export function RecentSubmissions({ submissions }: { submissions: HomeworkSubmissionListItem[] }) {
export function RecentSubmissions({
submissions,
title = "Recent Submissions",
emptyTitle = "No New Submissions",
emptyDescription = "All caught up! There are no new submissions to review."
}: {
submissions: HomeworkSubmissionListItem[],
title?: string,
emptyTitle?: string,
emptyDescription?: string
}) {
const hasSubmissions = submissions.length > 0;
return (
<Card className="col-span-4 lg:col-span-4">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Inbox className="h-4 w-4 text-muted-foreground" />
Recent Submissions
<Card className="h-full flex flex-col">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="flex items-center gap-2 text-lg">
<Inbox className="h-5 w-5 text-primary" />
{title}
</CardTitle>
<Button variant="ghost" size="sm" className="text-muted-foreground hover:text-primary" asChild>
<Link href="/teacher/homework/submissions" className="flex items-center gap-1">
View All <ArrowRight className="h-3 w-3" />
</Link>
</Button>
</CardHeader>
<CardContent>
<CardContent className="flex-1">
{!hasSubmissions ? (
<EmptyState
icon={Inbox}
title="No New Submissions"
description="All caught up! There are no new submissions to review."
title={emptyTitle}
description={emptyDescription}
action={{ label: "View submissions", href: "/teacher/homework/submissions" }}
className="border-none h-[300px]"
className="border-none h-full min-h-[200px]"
/>
) : (
<div className="space-y-6">
{submissions.map((item) => (
<div key={item.id} className="flex items-center justify-between group">
<div className="flex items-center space-x-4">
<Avatar className="h-9 w-9">
<AvatarImage src={undefined} alt={item.studentName} />
<AvatarFallback>{item.studentName.charAt(0)}</AvatarFallback>
</Avatar>
<div className="space-y-1">
<p className="text-sm font-medium leading-none">
{item.studentName}
</p>
<p className="text-sm text-muted-foreground">
<Link
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow className="bg-muted/50">
<TableHead className="w-[200px]">Student</TableHead>
<TableHead>Assignment</TableHead>
<TableHead className="w-[140px]">Submitted</TableHead>
<TableHead className="w-[100px] text-right">Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{submissions.map((item) => (
<TableRow key={item.id} className="hover:bg-muted/50">
<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>
</Avatar>
<span className="font-medium text-sm">{item.studentName}</span>
</div>
</TableCell>
<TableCell>
<Link
href={`/teacher/homework/submissions/${item.id}`}
className="font-medium text-foreground hover:underline"
className="font-medium hover:text-primary hover:underline transition-colors block truncate max-w-[240px]"
title={item.assignmentTitle}
>
{item.assignmentTitle}
</Link>
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<div className="text-sm text-muted-foreground">
{item.submittedAt ? formatDate(item.submittedAt) : "-"}
</div>
{item.isLate && (
<span className="inline-flex items-center rounded-full border border-destructive px-2 py-0.5 text-xs font-semibold text-destructive transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2">
Late
</span>
)}
</div>
</div>
))}
</TableCell>
<TableCell>
<div className="flex flex-col gap-1">
<span className="text-xs text-muted-foreground">
{item.submittedAt ? formatDate(item.submittedAt) : "-"}
</span>
{item.isLate && (
<Badge variant="destructive" className="w-fit text-[10px] h-4 px-1.5 font-normal">
Late
</Badge>
)}
</div>
</TableCell>
<TableCell className="text-right">
<Button size="sm" variant="secondary" className="h-8 px-3" asChild>
<Link href={`/teacher/homework/submissions/${item.id}`}>
Grade
</Link>
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</CardContent>