dashboard: - Add comparison-badge, dashboard-notification-widget, dashboard-responsive-layout, dashboard-time-range-filter - Add parent-dashboard components directory - Add config, hooks, and services directories diagnostic: - Add role-config and services directory elective: - Add elective-course-detail, elective-stats-cards, parent-selection-view components - Add data-access-settings and data-access-stats
113 lines
4.6 KiB
TypeScript
113 lines
4.6 KiB
TypeScript
import { use } from "react"
|
||
import type { TeacherDashboardData } from "@/modules/dashboard/types"
|
||
import type { TeacherDashboardMetrics } from "@/modules/dashboard/lib/dashboard-utils"
|
||
import type { ActionState } from "@/shared/types/action-state"
|
||
import { getTranslations } from "next-intl/server"
|
||
import type { TeacherTodoItem } from "./teacher-todo-card"
|
||
|
||
import { DashboardSection } from "../dashboard-section"
|
||
import { TeacherClassesCard } from "./teacher-classes-card"
|
||
import { TeacherDashboardHeader } from "./teacher-dashboard-header"
|
||
import { TeacherHomeworkCard } from "./teacher-homework-card"
|
||
import { RecentSubmissions } from "./recent-submissions"
|
||
import { TeacherSchedule } from "./teacher-schedule"
|
||
import { TeacherStats } from "./teacher-stats"
|
||
import { TeacherGradeTrends } from "./teacher-grade-trends"
|
||
import { TeacherTodoCard } from "./teacher-todo-card"
|
||
|
||
type TeacherDashboardResult = ActionState<TeacherDashboardData & { metrics: TeacherDashboardMetrics }>
|
||
|
||
/**
|
||
* 教师仪表盘视图(P2-1 流式架构)
|
||
*
|
||
* 接收未解析的 Promise,用 React `use()` 消费。
|
||
* 页面外壳立即渲染,数据到达后在 DashboardSection 的 Suspense 边界内填充。
|
||
*/
|
||
export function TeacherDashboardView({ dataPromise }: { dataPromise: Promise<TeacherDashboardResult> }) {
|
||
const result = use(dataPromise)
|
||
if (!result.success || !result.data) {
|
||
throw new Error(result.message ?? "Failed to load teacher dashboard")
|
||
}
|
||
|
||
return <TeacherDashboardContent data={result.data} />
|
||
}
|
||
|
||
async function TeacherDashboardContent({ data }: { data: TeacherDashboardData & { metrics: TeacherDashboardMetrics } }) {
|
||
const t = await getTranslations("dashboard")
|
||
const { metrics } = data
|
||
|
||
// 待办聚合(使用预计算指标)
|
||
const todoItems: TeacherTodoItem[] = [
|
||
{
|
||
label: t("todo.toGrade"),
|
||
count: metrics.toGradeCount,
|
||
href: "/teacher/homework/submissions",
|
||
variant: metrics.toGradeCount > 0 ? "urgent" : "normal",
|
||
},
|
||
{
|
||
label: t("todo.todayAttendance"),
|
||
count: metrics.todayScheduleItems.length,
|
||
href: "/teacher/attendance/sheet",
|
||
variant: "info",
|
||
},
|
||
{
|
||
label: t("todo.activeAssignments"),
|
||
count: metrics.activeAssignmentsCount,
|
||
href: "/teacher/homework/assignments",
|
||
variant: "normal",
|
||
},
|
||
]
|
||
|
||
return (
|
||
<div className="flex h-full flex-col space-y-6 p-8">
|
||
<header>
|
||
<TeacherDashboardHeader teacherName={data.teacherName} />
|
||
</header>
|
||
|
||
<DashboardSection variant="stats" ariaLabel={t("sections.quickStats")}>
|
||
<TeacherStats
|
||
toGradeCount={metrics.toGradeCount}
|
||
activeAssignmentsCount={metrics.activeAssignmentsCount}
|
||
averageScore={metrics.averageScore}
|
||
submissionRate={metrics.submissionRate}
|
||
/>
|
||
</DashboardSection>
|
||
|
||
<div className="flex flex-col gap-6 lg:grid lg:grid-cols-12">
|
||
{/* 课表:移动端首位,桌面端右上 — 仅渲染一次(P2-9 修复,原为双实例) */}
|
||
<div className="order-1 lg:col-start-9 lg:col-span-4 lg:row-start-1">
|
||
<DashboardSection variant="card" ariaLabel={t("sections.todaySchedule")}>
|
||
<TeacherSchedule items={metrics.todayScheduleItems} />
|
||
</DashboardSection>
|
||
</div>
|
||
|
||
<section aria-label={t("sections.pendingGrading")} className="flex flex-col gap-6 order-2 lg:col-start-1 lg:col-span-8 lg:row-start-1 lg:row-span-2">
|
||
<DashboardSection variant="card" ariaLabel={t("todo.title")}>
|
||
<TeacherTodoCard items={todoItems} />
|
||
</DashboardSection>
|
||
<DashboardSection variant="chart" ariaLabel={t("sections.gradeTrends")}>
|
||
<TeacherGradeTrends trends={data.gradeTrends} />
|
||
</DashboardSection>
|
||
<DashboardSection variant="list" ariaLabel={t("sections.recentSubmissions")}>
|
||
<RecentSubmissions
|
||
submissions={metrics.submissionsToGrade}
|
||
title={t("sections.pendingGrading")}
|
||
emptyTitle={t("empty.allGraded")}
|
||
emptyDescription={t("empty.allGradedDesc")}
|
||
/>
|
||
</DashboardSection>
|
||
</section>
|
||
|
||
<aside aria-label={t("sections.myClasses")} className="flex flex-col gap-6 order-3 lg:col-start-9 lg:col-span-4 lg:row-start-2">
|
||
<DashboardSection variant="list" ariaLabel={t("sections.homework")}>
|
||
<TeacherHomeworkCard assignments={data.assignments} />
|
||
</DashboardSection>
|
||
<DashboardSection variant="list" ariaLabel={t("sections.myClasses")}>
|
||
<TeacherClassesCard classes={data.classes} />
|
||
</DashboardSection>
|
||
</aside>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|