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
126 lines
4.2 KiB
TypeScript
126 lines
4.2 KiB
TypeScript
"use client";
|
||
|
||
import { Activity, Building2, GraduationCap, Users } from "lucide-react";
|
||
|
||
import { useAdminDashboard } 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 的 adminDashboard 真实聚合查询,替换原
|
||
* announcements 假契约 widget 查询。
|
||
*
|
||
* 关联:ARCHITECTURE.md §5.5 / §10 P1-2
|
||
*/
|
||
export default function AdminDashboardPage(): React.ReactElement {
|
||
const { data, loading, error } = useAdminDashboard();
|
||
|
||
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.total_teachers ?? "--"}
|
||
icon={Users}
|
||
/>
|
||
<StatCard
|
||
title="学生总数"
|
||
value={data.total_students ?? "--"}
|
||
icon={GraduationCap}
|
||
/>
|
||
<StatCard
|
||
title="班级总数"
|
||
value={data.total_classes ?? "--"}
|
||
icon={Building2}
|
||
/>
|
||
<StatCard
|
||
title="全校平均分"
|
||
value={data.school_avg_score?.toFixed(1) ?? "--"}
|
||
icon={Activity}
|
||
/>
|
||
</>
|
||
}
|
||
>
|
||
<DashboardSection title="近期预警" variant="list">
|
||
{data.recent_warnings ? (
|
||
<div className="space-y-2 text-sm">
|
||
<div className="flex items-center justify-between">
|
||
<span className="font-medium">
|
||
{data.recent_warnings.target_name ?? "--"}
|
||
</span>
|
||
<span className="text-muted-foreground">
|
||
{data.recent_warnings.severity ?? "--"}
|
||
</span>
|
||
</div>
|
||
<p className="text-xs text-muted-foreground">
|
||
类型:{data.recent_warnings.warning_type ?? "--"} · 当前值{" "}
|
||
{data.recent_warnings.current_value?.toFixed(1) ?? "--"} / 阈值{" "}
|
||
{data.recent_warnings.threshold?.toFixed(1) ?? "--"}
|
||
</p>
|
||
</div>
|
||
) : (
|
||
<p className="text-sm text-muted-foreground">暂无预警</p>
|
||
)}
|
||
</DashboardSection>
|
||
|
||
<DashboardSection title="AI 用量" variant="card">
|
||
{data.ai_usage ? (
|
||
<div className="space-y-2 text-sm">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-muted-foreground">总请求</span>
|
||
<span className="font-medium">
|
||
{data.ai_usage.total_requests ?? "--"}
|
||
</span>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-muted-foreground">总 Token</span>
|
||
<span className="font-medium">
|
||
{data.ai_usage.total_tokens ?? "--"}
|
||
</span>
|
||
</div>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-muted-foreground">总费用(分)</span>
|
||
<span className="font-medium">
|
||
{data.ai_usage.total_cost_cents ?? "--"}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<p className="text-sm text-muted-foreground">暂无 AI 用量数据</p>
|
||
)}
|
||
</DashboardSection>
|
||
</DashboardShell>
|
||
);
|
||
}
|