refactor(modules): update existing module implementations across attendance, audit, auth, classes, course-plans, exams, files, homework, layout, proctoring, questions, scheduling, textbooks, users

- Update attendance components and data-access for record management

- Update audit log views, filters, and data-access

- Update auth login and register forms

- Update classes actions, components, and data-access (admin, schedule, stats)

- Update course-plans actions, form, list, progress, and schema

- Update exams actions, AI pipeline, preview components, and hooks

- Update files components (icon, list, preview, upload) and data-access

- Update homework assignment form, review view, auto-save hook, and stats-service

- Update layout sidebar, header, and navigation config

- Update proctoring actions, anti-cheat monitor, and data-access

- Update questions actions, components (dialog, actions, columns, filters), and data-access

- Update scheduling actions, auto-scheduler, components, and schema

- Update textbooks constants and text-selection hook

- Update users class-registration, import-dialog, data-access, and user-service
This commit is contained in:
SpecialX
2026-06-23 17:38:56 +08:00
parent 1a9377222c
commit 4f0ef217a0
56 changed files with 1251 additions and 850 deletions

View File

@@ -6,6 +6,11 @@ import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guar
import { Permissions } from "@/shared/types/permissions"
import { z } from "zod"
import { createId } from "@paralleldrive/cuid2"
import {
handleActionError,
safeJsonParse,
} from "@/shared/lib/action-utils"
import { trackExamEvent } from "@/shared/lib/track-event"
import {
buildExamDescription,
deleteExamById,
@@ -73,12 +78,15 @@ const parseExamModeConfig = (formData: FormData): ExamModeConfig => {
const durationMinutes = rawDuration && Number.isFinite(Number(rawDuration))
? Number(rawDuration)
: null
const rawGrace = getStringValue(formData, "lateStartGraceMinutes") ?? "0"
const parsedGrace = Number(rawGrace)
const lateStartGraceMinutes = Number.isFinite(parsedGrace) ? parsedGrace : 0
return {
examMode,
durationMinutes,
shuffleQuestions: getBoolValue(formData, "shuffleQuestions", false),
allowLateStart: getBoolValue(formData, "allowLateStart", false),
lateStartGraceMinutes: Number(getStringValue(formData, "lateStartGraceMinutes") ?? "0") || 0,
lateStartGraceMinutes,
antiCheatEnabled: getBoolValue(formData, "antiCheatEnabled", false),
}
}
@@ -315,7 +323,7 @@ export async function createExamAction(
totalScore: getStringValue(formData, "totalScore"),
durationMin: getStringValue(formData, "durationMin"),
scheduledAt: getStringValue(formData, "scheduledAt") ?? null,
questions: rawQuestions ? JSON.parse(rawQuestions) : [],
questions: rawQuestions ? safeJsonParse(rawQuestions, "题目数据格式无效") : [],
})
if (!parsed.success) {
@@ -345,7 +353,7 @@ export async function createExamAction(
examModeConfig: parseExamModeConfig(formData),
})
} catch (error) {
console.error("Failed to create exam:", error)
console.error("[ExamAction]", error instanceof Error ? error.message : String(error))
return failState<string>("Database error: Failed to create exam")
}
@@ -356,7 +364,7 @@ export async function createExamAction(
if (error instanceof PermissionDeniedError) {
return failState<string>(error.message)
}
throw error
return handleActionError(error)
}
}
@@ -403,7 +411,7 @@ export async function createAiExamAction(
totalScore: getStringValue(formData, "totalScore"),
durationMin: getStringValue(formData, "durationMin"),
scheduledAt: getStringValue(formData, "scheduledAt") ?? null,
questions: rawQuestions ? JSON.parse(rawQuestions) : [],
questions: rawQuestions ? safeJsonParse(rawQuestions, "题目数据格式无效") : [],
aiSourceText: typeof aiSourceTextRaw === "string" ? aiSourceTextRaw.trim() : undefined,
aiQuestionCount: typeof aiQuestionCountRaw === "string" && aiQuestionCountRaw.trim().length > 0
? aiQuestionCountRaw
@@ -465,18 +473,28 @@ export async function createAiExamAction(
examModeConfig: parseExamModeConfig(formData),
})
} catch (error) {
console.error("Failed to create exam:", error)
console.error("[ExamAction]", error instanceof Error ? error.message : String(error))
return failState<string>("Database error: Failed to create exam")
}
revalidatePath("/teacher/exams/all")
// V3-4: 埋点监控AI 生成考试)
await trackExamEvent("exam.ai_generated", {
userId: ctx.userId,
targetId: context.examId,
properties: {
aiSourceText: input.aiSourceText?.length ?? 0,
aiQuestionCount: input.aiQuestionCount,
},
})
return successState(context.examId, "Exam created successfully.")
} catch (error) {
if (error instanceof PermissionDeniedError) {
return failState<string>(error.message)
}
throw error
return handleActionError(error)
}
}
@@ -529,7 +547,7 @@ export async function previewAiExamAction(
if (error instanceof PermissionDeniedError) {
return failState<AiPreviewData>(error.message)
}
throw error
return handleActionError(error)
}
}
@@ -565,14 +583,15 @@ export async function regenerateAiQuestionAction(
score: result.data.score ?? originalScore,
content: result.data.content,
})
} catch {
} catch (error) {
console.error("[ExamAction]", error instanceof Error ? error.message : String(error))
return failState<AiRewriteQuestionData>("AI question format invalid")
}
} catch (error) {
if (error instanceof PermissionDeniedError) {
return failState<AiRewriteQuestionData>(error.message)
}
throw error
return handleActionError(error)
}
}
@@ -599,13 +618,13 @@ export async function updateExamAction(
const rawQuestions = formData.get("questionsJson")
const rawStructure = formData.get("structureJson")
const hasQuestions = typeof rawQuestions === "string"
const hasStructure = typeof rawStructure === "string"
const rawQuestionsStr = typeof rawQuestions === "string" ? rawQuestions : null
const rawStructureStr = typeof rawStructure === "string" ? rawStructure : null
const parsed = ExamUpdateSchema.safeParse({
examId: formData.get("examId"),
questions: hasQuestions ? JSON.parse(rawQuestions) : undefined,
structure: hasStructure ? JSON.parse(rawStructure) : undefined,
questions: rawQuestionsStr ? safeJsonParse(rawQuestionsStr, "题目数据格式无效") : undefined,
structure: rawStructureStr ? safeJsonParse(rawStructureStr, "试卷结构数据格式无效") : undefined,
status: formData.get("status") ?? undefined,
})
@@ -632,18 +651,26 @@ export async function updateExamAction(
structure,
status,
})
} catch {
} catch (error) {
console.error("[ExamAction]", error instanceof Error ? error.message : String(error))
return failState<string>("Database error: Failed to update exam")
}
revalidatePath("/teacher/exams/all")
// V3-4: 埋点监控
await trackExamEvent("exam.updated", {
userId: ctx.userId,
targetId: examId,
properties: { hasQuestions: !!questions, hasStructure: !!structure, status },
})
return successState(examId, "Exam updated")
} catch (error) {
if (error instanceof PermissionDeniedError) {
return failState<string>(error.message)
}
throw error
return handleActionError(error)
}
}
@@ -681,18 +708,25 @@ export async function deleteExamAction(
try {
await deleteExamById(examId)
} catch {
} catch (error) {
console.error("[ExamAction]", error instanceof Error ? error.message : String(error))
return failState<string>("Database error: Failed to delete exam")
}
revalidatePath("/teacher/exams/all")
// V3-4: 埋点监控
await trackExamEvent("exam.deleted", {
userId: ctx.userId,
targetId: examId,
})
return successState(examId, "Exam deleted")
} catch (error) {
if (error instanceof PermissionDeniedError) {
return failState<string>(error.message)
}
throw error
return handleActionError(error)
}
}
@@ -727,18 +761,26 @@ export async function duplicateExamAction(
return failState<string>("Exam not found")
}
newExamId = duplicatedId
} catch {
} catch (error) {
console.error("[ExamAction]", error instanceof Error ? error.message : String(error))
return failState<string>("Database error: Failed to duplicate exam")
}
revalidatePath("/teacher/exams/all")
// V3-4: 埋点监控
await trackExamEvent("exam.duplicated", {
userId: ctx.userId,
targetId: newExamId,
properties: { sourceExamId: examId },
})
return successState(newExamId, "Exam duplicated")
} catch (error) {
if (error instanceof PermissionDeniedError) {
return failState<string>(error.message)
}
throw error
return handleActionError(error)
}
}
@@ -759,14 +801,14 @@ export async function getExamPreviewAction(
questions: exam.questions,
})
} catch (error) {
console.error(error)
console.error("[ExamAction]", error instanceof Error ? error.message : String(error))
return failState<{ structure: unknown; questions: Array<{ id: string }> }>("Failed to load exam preview")
}
} catch (error) {
if (error instanceof PermissionDeniedError) {
return failState<{ structure: unknown; questions: Array<{ id: string }> }>(error.message)
}
throw error
return handleActionError(error)
}
}
@@ -778,14 +820,14 @@ export async function getSubjectsAction(): Promise<ActionState<{ id: string; nam
const allSubjects = await getExamSubjects()
return successState(allSubjects)
} catch (error) {
console.error("Failed to fetch subjects:", error)
console.error("[ExamAction]", error instanceof Error ? error.message : String(error))
return failState<{ id: string; name: string }[]>("Failed to load subjects")
}
} catch (error) {
if (error instanceof PermissionDeniedError) {
return failState<{ id: string; name: string }[]>(error.message)
}
throw error
return handleActionError(error)
}
}
@@ -797,14 +839,14 @@ export async function getGradesAction(): Promise<ActionState<{ id: string; name:
const allGrades = await getExamGrades()
return successState(allGrades)
} catch (error) {
console.error("Failed to fetch grades:", error)
console.error("[ExamAction]", error instanceof Error ? error.message : String(error))
return failState<{ id: string; name: string }[]>("Failed to load grades")
}
} catch (error) {
if (error instanceof PermissionDeniedError) {
return failState<{ id: string; name: string }[]>(error.message)
}
throw error
return handleActionError(error)
}
}