ARCHITECTURE.md §10 P1-7: dashboard domain's 6 operations strictly match the schema, so disable skipDocumentsValidation for that output and restore per-operation type generation. Changes: - codegen.yml: add dashboard-types.ts output (typescript + typescript-operations plugins, skipDocumentsValidation: false); move documents config into each generates entry - dashboard.ts: remove 14 handwritten interfaces and 6 internal query type aliases; derive types via NonNullable<GetXxxQuery['xxx']> so the public hook API shape stays unchanged - admin/student/teacher page.tsx: add ?? "--" / ?? 0 null guards on StatCard value props to match schema nullable semantics (parent page already uses toFixed chain, no change needed) Acceptance (ARCHITECTURE.md §10 P1-7): - codegen 3 outputs all SUCCESS - tsc 0 errors / eslint 0 errors / vitest 231 passed / next build ok - 6 operations strictly match schema with 0 errors Refs: ARCHITECTURE.md §5.3 data layer / §10 P1-7
108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
"use client";
|
||
|
||
import { BookOpen, GraduationCap, TrendingUp } from "lucide-react";
|
||
|
||
import { useStudentDashboard } from "@/lib/api";
|
||
import { DashboardShell } from "@/shared/components/dashboard/dashboard-shell";
|
||
import { DashboardSection } from "@/shared/components/dashboard/dashboard-section";
|
||
import { StatCard } from "@/shared/components/ui/stat-card";
|
||
import { Card, CardContent } from "@/shared/components/ui/card";
|
||
|
||
/**
|
||
* 学生仪表盘(ARCHITECTURE.md §7.1 / §10 P1-2)
|
||
*
|
||
* 改接 data-ana 的 studentDashboard 真实聚合查询,替换原
|
||
* grades/homeworks/schedule/exams 假契约 widget 查询。
|
||
*
|
||
* 关联:ARCHITECTURE.md §5.5 / §10 P1-2
|
||
*/
|
||
export default function StudentDashboardPage(): React.ReactElement {
|
||
const { data, loading, error } = useStudentDashboard();
|
||
|
||
if (loading) {
|
||
return (
|
||
<DashboardShell title="学生仪表盘" description="学习概览">
|
||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||
{Array.from({ length: 4 }).map((_, i) => (
|
||
<StatCard key={i} title="" value="" isLoading />
|
||
))}
|
||
</div>
|
||
</DashboardShell>
|
||
);
|
||
}
|
||
|
||
if (error || !data) {
|
||
return (
|
||
<DashboardShell title="学生仪表盘" description="学习概览">
|
||
<Card>
|
||
<CardContent className="py-8 text-center text-muted-foreground">
|
||
仪表盘数据加载失败,请稍后重试。
|
||
</CardContent>
|
||
</Card>
|
||
</DashboardShell>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<DashboardShell
|
||
title="学生仪表盘"
|
||
description="学习概览"
|
||
stats={
|
||
<>
|
||
<StatCard
|
||
title="平均分"
|
||
value={data.avg_score?.toFixed(1) ?? "--"}
|
||
icon={GraduationCap}
|
||
/>
|
||
<StatCard
|
||
title="班级排名"
|
||
value={`${data.class_rank ?? "--"} / ${data.total_students ?? "--"}`}
|
||
icon={TrendingUp}
|
||
/>
|
||
<StatCard
|
||
title="待交作业"
|
||
value={data.pending_homework ?? 0}
|
||
icon={BookOpen}
|
||
highlight={(data.pending_homework ?? 0) > 0}
|
||
/>
|
||
</>
|
||
}
|
||
>
|
||
<DashboardSection title="薄弱知识点" variant="list">
|
||
{data.weak_points ? (
|
||
<div className="space-y-2 text-sm">
|
||
<div className="flex items-center justify-between">
|
||
<span className="font-medium">
|
||
{data.weak_points.title ?? "--"}
|
||
</span>
|
||
<span className="text-muted-foreground">
|
||
掌握度 {data.weak_points.mastery?.toFixed(1) ?? "--"}%
|
||
</span>
|
||
</div>
|
||
<p className="text-xs text-muted-foreground">
|
||
错误次数:{data.weak_points.error_count ?? 0}
|
||
</p>
|
||
</div>
|
||
) : (
|
||
<p className="text-sm text-muted-foreground">暂无薄弱知识点数据</p>
|
||
)}
|
||
</DashboardSection>
|
||
|
||
<DashboardSection title="近期成绩趋势" variant="chart">
|
||
{data.recent_trends ? (
|
||
<div className="text-sm">
|
||
<span className="text-muted-foreground">
|
||
{data.recent_trends.date ?? "--"}:
|
||
</span>
|
||
<span className="font-medium">
|
||
{data.recent_trends.score?.toFixed(1) ?? "--"} 分
|
||
</span>
|
||
</div>
|
||
) : (
|
||
<p className="text-sm text-muted-foreground">暂无趋势数据</p>
|
||
)}
|
||
</DashboardSection>
|
||
</DashboardShell>
|
||
);
|
||
}
|