Files
Edu/apps/portal-shell/src/app/shell/admin/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

126 lines
4.2 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 { 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>
);
}