Files
NextEdu/src/modules/dashboard/components/student-dashboard/student-grades-card.tsx
SpecialX 1a9377222c 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
2026-06-23 17:38:28 +08:00

117 lines
4.4 KiB
TypeScript

"use client"
import Link from "next/link"
import { BarChart3 } from "lucide-react"
import { useTranslations, useLocale } from "next-intl"
import { Button } from "@/shared/components/ui/button"
import { ChartCardShell } from "@/shared/components/charts/chart-card-shell"
import { TrendLineChart } from "@/shared/components/charts/trend-line-chart"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/shared/components/ui/table"
import { formatDate } from "@/shared/lib/utils"
import type { StudentDashboardGradeProps } from "@/modules/homework/types"
export function StudentGradesCard({ grades }: { grades: StudentDashboardGradeProps }) {
const t = useTranslations("dashboard")
const locale = useLocale()
const hasGradeTrend = grades.trend.length > 0
const hasRecentGrades = grades.recent.length > 0
const chartData = grades.trend.map((item) => ({
title: item.assignmentTitle,
score: Math.round(item.percentage),
fullTitle: item.assignmentTitle,
submittedAt: formatDate(item.submittedAt, locale),
rawScore: item.score,
maxScore: item.maxScore,
}))
const latestGrade = grades.trend[grades.trend.length - 1]
return (
<ChartCardShell
title={t("sections.recentGrades")}
icon={BarChart3}
iconClassName="text-muted-foreground"
isEmpty={!hasGradeTrend}
emptyTitle={t("empty.noGradedWork")}
emptyDescription={t("empty.noGradedWorkDesc")}
emptyClassName="h-72"
action={
hasGradeTrend ? (
<Button asChild variant="outline" size="sm">
<Link href="/student/grades">{t("quickActions.viewAll")}</Link>
</Button>
) : null
}
>
<div className="space-y-4">
<div className="rounded-md border bg-card p-4">
<TrendLineChart
data={chartData}
series={[
{
dataKey: "score",
name: t("chart.scorePercent"),
color: "hsl(var(--primary))",
dotRadius: 4,
activeDotRadius: 6,
},
]}
heightClassName="h-[200px]"
margin={{ left: 12, right: 12, top: 12, bottom: 12 }}
yWidth={30}
tooltipClassName="w-[200px]"
/>
{latestGrade ? (
<div className="mt-3 flex items-center justify-between text-sm text-muted-foreground">
<div>
{t("chart.latest")}:{" "}
<span className="font-medium text-foreground tabular-nums">
{Math.round(latestGrade.percentage)}%
</span>
</div>
<div>
{t("chart.points")}:{" "}
<span className="font-medium text-foreground tabular-nums">
{latestGrade.score}/{latestGrade.maxScore}
</span>
</div>
</div>
) : null}
</div>
{!hasRecentGrades ? null : (
<div className="rounded-md border bg-card">
<Table>
<TableHeader>
<TableRow className="bg-muted/50">
<TableHead className="text-xs font-medium uppercase text-muted-foreground">{t("table.assignment")}</TableHead>
<TableHead className="text-xs font-medium uppercase text-muted-foreground">{t("table.score")}</TableHead>
<TableHead className="text-xs font-medium uppercase text-muted-foreground">{t("table.when")}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{grades.recent.map((r) => (
<TableRow key={r.assignmentId} className="h-12">
<TableCell className="font-medium">
<Link href={`/student/learning/assignments/${r.assignmentId}`} className="hover:underline">
{r.assignmentTitle}
</Link>
</TableCell>
<TableCell className="tabular-nums">
{r.score}/{r.maxScore} <span className="text-muted-foreground">({Math.round(r.percentage)}%)</span>
</TableCell>
<TableCell className="text-muted-foreground">{formatDate(r.submittedAt, locale)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</div>
</ChartCardShell>
)
}