Files
Edu/apps/teacher-portal/src/app/(app)/dashboard/page.tsx
SpecialX 1eacd1ed87 feat(teacher-portal): 完整实现 teacher-portal 微前端
包含 settings/students/api、graphql、mocks、ui-tokens 设计令牌等
2026-07-10 19:10:20 +08:00

147 lines
4.5 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";
/**
* Dashboard 页面 - 教师仪表盘
*
* 数据来源ARB-001 §1.2GraphQL DashboardQuery
* - 并行聚合iam.me + iam.viewports + iam.permissions + classes.byTeacher
* - 返回user + classes + viewports + stats
*
* 维护者ai13teacher-portal
*/
import { useQuery } from "urql";
import Link from "next/link";
import { Loading, Empty } from "@edu/ui-components";
import { DashboardQuery } from "@/lib/graphql";
import type { Class } from "@/lib/graphql";
export default function DashboardPage() {
const [result] = useQuery({ query: DashboardQuery });
if (result.fetching) {
return (
<div className="px-10 py-10">
<Loading lines={5} />
</div>
);
}
if (result.error) {
return (
<div className="px-10 py-10">
<header className="mb-8">
<h1 className="text-3xl font-serif text-ink"></h1>
</header>
<div className="rule-thin mb-8" />
<div className="mark-left py-2 mb-4 border-l-2 border-danger pl-md">
<p className="text-sm px-3 text-danger">
{result.error.message}
</p>
</div>
</div>
);
}
const data = result.data?.dashboard;
if (!data) {
return (
<div className="px-10 py-10">
<Empty title="暂无数据" description="仪表盘数据尚未就绪" />
</div>
);
}
const { user, classes, stats } = data;
return (
<div className="px-10 py-10">
<header className="mb-8">
<h1 className="text-3xl font-serif text-ink">{user.name}</h1>
<p className="mt-1 text-sm text-ink-muted">
{user.email} · {user.roles.join(", ") || "无"} ·
{user.dataScope}
</p>
</header>
<div className="rule-thin mb-8" />
{/* 统计卡片 */}
<section className="grid grid-cols-3 gap-6 mb-10">
<div className="p-6 border border-rule rounded-card">
<p className="text-tiny uppercase tracking-wide text-ink-muted">
</p>
<p className="mt-3 text-4xl font-serif text-ink">{classes.length}</p>
</div>
<div className="p-6 border border-rule rounded-card">
<p className="text-tiny uppercase tracking-wide text-ink-muted">
</p>
<p className="mt-3 text-4xl font-serif text-ink">
{stats.totalExams}
</p>
</div>
<div className="p-6 border border-rule rounded-card">
<p className="text-tiny uppercase tracking-wide text-ink-muted">
</p>
<p className="mt-3 text-4xl font-serif text-accent">
{stats.pendingGrading}
</p>
</div>
</section>
{/* 班级快览 */}
<section>
<div className="flex items-baseline justify-between mb-4">
<h2 className="text-xl font-serif text-ink">
<span className="ml-2 text-sm font-sans text-ink-muted">
{classes.length}
</span>
</h2>
<Link
href="/classes"
className="text-tiny uppercase tracking-wide text-accent hover:opacity-70"
>
</Link>
</div>
<div className="rule-thin mb-6" />
{classes.length === 0 ? (
<Empty title="暂无班级" description="请联系管理员分配班级" />
) : (
<ul className="space-y-0">
{(classes as Class[]).slice(0, 5).map((cls) => (
<li
key={cls.id}
className="py-4 grid grid-cols-12 gap-4 items-baseline border-b border-rule"
>
<div className="col-span-8">
<h3 className="text-lg font-serif text-ink">{cls.name}</h3>
<p className="mt-1 text-tiny text-ink-muted">
{cls.gradeId}
</p>
</div>
<div className="col-span-2 text-sm text-ink-muted">
{cls.studentCount}
</div>
<div className="col-span-2 text-right">
<Link
href={`/students?classId=${cls.id}`}
className="text-tiny uppercase tracking-wide text-accent hover:opacity-70"
>
</Link>
</div>
</li>
))}
</ul>
)}
</section>
</div>
);
}