Files
Edu/apps/portal-shell/src/app/shell/student/page.tsx
SpecialX 0beeff6329 feat(portal-shell): restore codegen typescript-operations for data-ana domain (P1-7)
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
2026-07-22 15:37:06 +08:00

108 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>
);
}