feat(portal-shell): wire dashboards to real data-ana queries (P1-2)
- add dashboard.graphql.ts with 6 real aggregate queries
(teacherDashboard / studentDashboard / parentDashboard /
adminDashboard / warnings / errorBookStats), snake_case aligned
- add dashboard.ts with 6 hooks + full domain model types
- add 4 role dashboard pages (teacher/student/parent/admin)
using DashboardShell + StatCard + DashboardSection with
loading / error / success tri-state
- update [[...route]]/page.tsx to redirect /shell -> /shell/{role}
- retire 6 fake contract queries and hooks (grades/homeworks/
schedule/attendance/exams/announcements) and mark widget
placeholders as migrated
- update universal.test.ts to drop retired hook tests
- mark ARCHITECTURE.md P1-2 as completed with acceptance evidence
This commit is contained in:
121
apps/portal-shell/src/app/shell/admin/page.tsx
Normal file
121
apps/portal-shell/src/app/shell/admin/page.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user