- 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
102 lines
3.7 KiB
TypeScript
102 lines
3.7 KiB
TypeScript
import { getTranslations } from "next-intl/server"
|
||
import { getAuthContext } from "@/shared/lib/auth-guard"
|
||
import { CalendarCheck } from "lucide-react"
|
||
|
||
import { createAttendanceReadService } from "@/modules/attendance/services/attendance-data-service"
|
||
import type { AttendanceReadService } from "@/modules/attendance/services/types"
|
||
import {
|
||
ParentChildrenDataPage,
|
||
ParentNoChildrenPage,
|
||
} from "@/modules/parent/components/parent-children-data-page"
|
||
import { ParentAttendanceWarning } from "@/modules/parent/components/parent-attendance-warning"
|
||
import { ParentAttendanceRateCard } from "@/modules/parent/components/parent-attendance-rate-card"
|
||
import { ParentAttendanceCalendar } from "@/modules/parent/components/parent-attendance-calendar"
|
||
import { ParentStudentAttendanceDetail } from "@/modules/parent/components/parent-student-attendance-detail"
|
||
import type { ParentStudentAttendanceSummary } from "@/modules/parent/types"
|
||
|
||
export const dynamic = "force-dynamic"
|
||
|
||
/**
|
||
* 将 attendance 模块的 StudentAttendanceSummary 映射为 parent 模块的视图模型。
|
||
* parent 模块仅消费自身类型,不依赖 attendance 内部类型变更(P1-2 解耦)。
|
||
*/
|
||
function toParentSummary(
|
||
summary: Awaited<ReturnType<AttendanceReadService["getStudentSummary"]>>,
|
||
): ParentStudentAttendanceSummary | null {
|
||
if (!summary) return null
|
||
return {
|
||
studentId: summary.studentId,
|
||
studentName: summary.studentName,
|
||
stats: {
|
||
total: summary.stats.total,
|
||
present: summary.stats.present,
|
||
absent: summary.stats.absent,
|
||
late: summary.stats.late,
|
||
presentRate: summary.stats.presentRate,
|
||
},
|
||
recentRecords: summary.recentRecords.map((r) => ({
|
||
id: r.id,
|
||
date: r.date,
|
||
status: r.status,
|
||
reason: r.reason,
|
||
remark: r.remark,
|
||
})),
|
||
}
|
||
}
|
||
|
||
export default async function ParentAttendancePage() {
|
||
const t = await getTranslations("attendance")
|
||
const ctx = await getAuthContext()
|
||
|
||
if (ctx.dataScope.type !== "children" || ctx.dataScope.childrenIds.length === 0) {
|
||
return (
|
||
<ParentNoChildrenPage
|
||
title={t("title.parent")}
|
||
description={t("description.parent")}
|
||
icon={CalendarCheck}
|
||
emptyTitle={t("parent.noChildrenTitle")}
|
||
emptyDescription={t("parent.noChildrenDescription")}
|
||
/>
|
||
)
|
||
}
|
||
|
||
// 通过接口抽象消费 attendance 数据(P1-2 修复:不再直接 import data-access)
|
||
const attendanceService = createAttendanceReadService(ctx.dataScope)
|
||
|
||
// 使用 allSettled 容错:单个子女查询失败不影响其他子女展示
|
||
const results = await Promise.allSettled(
|
||
ctx.dataScope.childrenIds.map((id) => attendanceService.getStudentSummary(id)),
|
||
)
|
||
const validSummaries = results
|
||
.map((r) =>
|
||
r.status === "fulfilled" ? toParentSummary(r.value) : null,
|
||
)
|
||
.filter((s): s is ParentStudentAttendanceSummary => s !== null)
|
||
|
||
return (
|
||
<ParentChildrenDataPage
|
||
title={t("title.parent")}
|
||
description={t("parent.compareDescription")}
|
||
icon={CalendarCheck}
|
||
noRecordsTitle={t("list.empty")}
|
||
noRecordsDescription={t("parent.noRecordsDescription")}
|
||
items={validSummaries}
|
||
renderItem={(summary) => (
|
||
<div className="space-y-6">
|
||
<h3 className="text-lg font-semibold border-b pb-2">{summary.studentName}</h3>
|
||
<ParentAttendanceCalendar summary={summary} />
|
||
<ParentStudentAttendanceDetail summary={summary} />
|
||
</div>
|
||
)}
|
||
headerExtra={
|
||
validSummaries.length > 0 ? (
|
||
<div className="space-y-4">
|
||
<ParentAttendanceRateCard summaries={validSummaries} />
|
||
<ParentAttendanceWarning summaries={validSummaries} />
|
||
</div>
|
||
) : null
|
||
}
|
||
/>
|
||
)
|
||
}
|