Files
NextEdu/src/app/(dashboard)/teacher/attendance/stats/page.tsx
SpecialX 21142f9b99 feat(app): add error/loading boundaries across all dashboard routes and new routes
- Add error.tsx and loading.tsx boundaries for admin, parent, student, teacher routes

- Add admin announcements edit, audit-logs overview, curriculum-map, invitation-codes, permissions, questions, roles routes

- Add admin elective detail and components, files, course-plans, users, scheduling boundaries

- Add messages group-compose route

- Add parent course-plans, elective, grades report-card, practice routes

- Add student course-plans, elective detail, error-book dialogs, grades report-card, learning study-path, leave, schedule boundaries

- Add teacher attendance report, classes boundaries, course-plans boundaries, elective, exams analytics/edit-rich/all/create/new, grades report-card, homework boundaries, leave, lesson-plans calendar

- Add auth loading, onboarding loading, api cron
2026-07-03 10:26:25 +08:00

113 lines
4.4 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.
import type { JSX } from "react"
import Link from "next/link"
import { getTranslations } from "next-intl/server"
import { requirePermission } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions"
import { getTeacherClasses } from "@/modules/classes/data-access"
import { getClassAttendanceStats, getClassAttendanceWarnings, getAttendanceTrend } from "@/modules/attendance/data-access-stats"
import { AttendanceStatsCard } from "@/modules/attendance/components/attendance-stats-card"
import { AttendanceRecordList } from "@/modules/attendance/components/attendance-record-list"
import { AttendanceStatsClassSelector } from "@/modules/attendance/components/attendance-stats-class-selector"
import { AttendanceWarningsCard } from "@/modules/attendance/components/attendance-warnings-card"
import { AttendanceTrendChart } from "@/modules/attendance/components/attendance-trend-chart"
import type { TrendGranularity } from "@/modules/attendance/trend-compute"
import { Button } from "@/shared/components/ui/button"
import { EmptyState } from "@/shared/components/ui/empty-state"
import { getParam, type SearchParams } from "@/shared/lib/search-params"
import { BarChart3, FileText } from "lucide-react"
export const dynamic = "force-dynamic"
export default async function AttendanceStatsPage({
searchParams,
}: {
searchParams: Promise<SearchParams>
}): Promise<JSX.Element> {
await requirePermission(Permissions.ATTENDANCE_READ)
const t = await getTranslations("attendance")
const sp = await searchParams
const classId = getParam(sp, "classId")
const startDate = getParam(sp, "startDate")
const endDate = getParam(sp, "endDate")
// L-4趋势粒度默认 weekly
const granularityParam = getParam(sp, "granularity")
const granularity: TrendGranularity =
granularityParam === "daily" || granularityParam === "monthly"
? granularityParam
: "weekly"
const classes = await getTeacherClasses()
if (classes.length === 0) {
return (
<div className="h-full flex-1 flex-col space-y-8 p-8 md:flex">
<div>
<h1 className="text-2xl font-bold tracking-tight">{t("title.teacherStats")}</h1>
<p className="text-muted-foreground">{t("description.teacherStats")}</p>
</div>
<EmptyState
title={t("stats.noClasses")}
description={t("stats.noClassesDescription")}
icon={BarChart3}
className="border-none shadow-none"
/>
</div>
)
}
const targetClassId = classId ?? classes[0].id
// 并行获取统计、预警、趋势三个独立查询
const [summary, warnings, trend] = await Promise.all([
getClassAttendanceStats(targetClassId, startDate, endDate),
getClassAttendanceWarnings(targetClassId, startDate, endDate),
getAttendanceTrend(targetClassId, granularity, startDate, endDate),
])
const classOptions = classes.map((c) => ({ id: c.id, name: c.name }))
return (
<div className="h-full flex-1 flex-col space-y-8 p-8 md:flex">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold tracking-tight">{t("title.teacherStats")}</h1>
<p className="text-muted-foreground">{t("description.teacherStats")}</p>
</div>
<Button asChild variant="outline">
<Link href={`/teacher/attendance/report?classId=${targetClassId}${startDate ? `&startDate=${startDate}` : ""}${endDate ? `&endDate=${endDate}` : ""}`}>
<FileText className="mr-2 h-4 w-4" />
{t("report.title")}
</Link>
</Button>
</div>
<AttendanceStatsClassSelector
classes={classOptions}
currentClassId={targetClassId}
startDate={startDate ?? ""}
endDate={endDate ?? ""}
/>
{summary ? (
<>
<AttendanceStatsCard stats={summary.stats} />
<AttendanceWarningsCard summary={warnings} />
<AttendanceTrendChart summary={trend} granularity={granularity} />
<div>
<h2 className="mb-4 text-lg font-semibold">{t("stats.studentRecords")}</h2>
<AttendanceRecordList records={summary.studentRecords} />
</div>
</>
) : (
<EmptyState
title={t("stats.noData")}
description={t("stats.noDataDescription")}
icon={BarChart3}
className="border-none shadow-none"
/>
)}
</div>
)
}