import type { JSX } from "react" import { Suspense } from "react" import { getTranslations } from "next-intl/server" import { requirePermission } from "@/shared/lib/auth-guard" import { Permissions } from "@/shared/types/permissions" import { getParam, type SearchParams } from "@/shared/lib/search-params" import { Skeleton } from "@/shared/components/ui/skeleton" import { getErrorBookItems, getErrorBookStats } from "@/modules/error-book/data-access" import { ErrorBookStatsCards } from "@/modules/error-book/components/error-book-stats-cards" import { ErrorBookFilters } from "@/modules/error-book/components/error-book-filters" import { ErrorBookList } from "@/modules/error-book/components/error-book-list" import { AddErrorBookDialog } from "@/modules/error-book/components/add-error-book-dialog" import type { ErrorBookStatusValue, ErrorBookSourceTypeValue } from "@/modules/error-book/types" import { AiClientProvider, type AiClientService, } from "@/modules/ai/context/ai-client-provider" import { aiChatAction, suggestSimilarQuestionsAction, suggestGradingAction, generateLessonContentAction, generateQuestionVariantAction, analyzeWeaknessAction, } from "@/modules/ai/actions" export const dynamic = "force-dynamic" const VALID_STATUS = new Set(["new", "learning", "mastered", "archived"]) const VALID_SOURCE = new Set(["exam", "homework", "manual"]) function parseStatus(v?: string): ErrorBookStatusValue | undefined { return v && VALID_STATUS.has(v) ? (v as ErrorBookStatusValue) : undefined } function parseSource(v?: string): ErrorBookSourceTypeValue | undefined { return v && VALID_SOURCE.has(v) ? (v as ErrorBookSourceTypeValue) : undefined } /** * 构建 AI 客户端服务(Server Action 引用集合) * * 通过 React Context 注入,客户端组件不直接 import actions, * 遵循依赖注入模式,便于测试时替换为 mock。 */ function createAiClientService(): AiClientService { return { chat: aiChatAction, suggestSimilarQuestions: suggestSimilarQuestionsAction, suggestGrading: suggestGradingAction, generateLessonContent: generateLessonContentAction, generateQuestionVariant: generateQuestionVariantAction, analyzeWeakness: analyzeWeaknessAction, } } async function ErrorBookResults({ searchParams }: { searchParams: Promise }): Promise { const params = await searchParams const ctx = await requirePermission(Permissions.ERROR_BOOK_READ) const q = getParam(params, "q") const status = parseStatus(getParam(params, "status")) const sourceType = parseSource(getParam(params, "source")) const dueOnly = getParam(params, "due") === "due" const { data: items } = await getErrorBookItems({ studentId: ctx.userId, q: q || undefined, status, sourceType, dueOnly, pageSize: 50, }) return } function ErrorBookResultsFallback() { return (
{Array.from({ length: 4 }).map((_, idx) => ( ))}
) } export default async function StudentErrorBookPage({ searchParams, }: { searchParams: Promise }): Promise { const ctx = await requirePermission(Permissions.ERROR_BOOK_READ) const t = await getTranslations("student") const stats = await getErrorBookStats(ctx.userId) const aiClientService = createAiClientService() return (

{t("errorBook.title")}

{t("errorBook.description")}

}> }>
) }