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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user