feat: exam actions and data safety fixes
This commit is contained in:
@@ -16,38 +16,93 @@ export default async function BuildExamPage({ params }: { params: Promise<{ id:
|
||||
// In a real app, this might be paginated or filtered by exam subject/grade
|
||||
const { data: questionsData } = await getQuestions({ pageSize: 100 })
|
||||
|
||||
const questionOptions: Question[] = questionsData.map((q) => ({
|
||||
id: q.id,
|
||||
content: q.content as any,
|
||||
type: q.type as any,
|
||||
difficulty: q.difficulty ?? 1,
|
||||
createdAt: new Date(q.createdAt),
|
||||
updatedAt: new Date(q.updatedAt),
|
||||
author: q.author ? {
|
||||
id: q.author.id,
|
||||
name: q.author.name || "Unknown",
|
||||
image: q.author.image || null
|
||||
} : null,
|
||||
knowledgePoints: (q.questionsToKnowledgePoints || []).map((kp) => ({
|
||||
id: kp.knowledgePoint.id,
|
||||
name: kp.knowledgePoint.name
|
||||
}))
|
||||
}))
|
||||
|
||||
const initialSelected = (exam.questions || []).map(q => ({
|
||||
id: q.id,
|
||||
score: q.score || 0
|
||||
}))
|
||||
|
||||
// Prepare initialStructure on server side to avoid hydration mismatch with random IDs
|
||||
let initialStructure: ExamNode[] = exam.structure as ExamNode[] || []
|
||||
const selectedQuestionIds = initialSelected.map((s) => s.id)
|
||||
const { data: selectedQuestionsData } = selectedQuestionIds.length
|
||||
? await getQuestions({ ids: selectedQuestionIds, pageSize: Math.max(10, selectedQuestionIds.length) })
|
||||
: { data: [] as typeof questionsData }
|
||||
|
||||
type RawQuestion = (typeof questionsData)[number]
|
||||
|
||||
const toQuestionOption = (q: RawQuestion): Question => ({
|
||||
id: q.id,
|
||||
content: q.content as Question["content"],
|
||||
type: q.type as Question["type"],
|
||||
difficulty: q.difficulty ?? 1,
|
||||
createdAt: new Date(q.createdAt),
|
||||
updatedAt: new Date(q.updatedAt),
|
||||
author: q.author
|
||||
? {
|
||||
id: q.author.id,
|
||||
name: q.author.name || "Unknown",
|
||||
image: q.author.image || null,
|
||||
}
|
||||
: null,
|
||||
knowledgePoints:
|
||||
q.questionsToKnowledgePoints?.map((kp) => ({
|
||||
id: kp.knowledgePoint.id,
|
||||
name: kp.knowledgePoint.name,
|
||||
})) ?? [],
|
||||
})
|
||||
|
||||
const questionOptionsById = new Map<string, Question>()
|
||||
for (const q of questionsData) questionOptionsById.set(q.id, toQuestionOption(q))
|
||||
for (const q of selectedQuestionsData) questionOptionsById.set(q.id, toQuestionOption(q))
|
||||
const questionOptions = Array.from(questionOptionsById.values())
|
||||
|
||||
const normalizeStructure = (nodes: unknown): ExamNode[] => {
|
||||
const seen = new Set<string>()
|
||||
const isRecord = (v: unknown): v is Record<string, unknown> =>
|
||||
typeof v === "object" && v !== null
|
||||
|
||||
const normalize = (raw: unknown[]): ExamNode[] => {
|
||||
return raw
|
||||
.map((n) => {
|
||||
if (!isRecord(n)) return null
|
||||
const type = n.type
|
||||
if (type !== "group" && type !== "question") return null
|
||||
|
||||
let id = typeof n.id === "string" && n.id.length > 0 ? n.id : createId()
|
||||
while (seen.has(id)) id = createId()
|
||||
seen.add(id)
|
||||
|
||||
if (type === "group") {
|
||||
return {
|
||||
id,
|
||||
type: "group",
|
||||
title: typeof n.title === "string" ? n.title : undefined,
|
||||
children: normalize(Array.isArray(n.children) ? n.children : []),
|
||||
} satisfies ExamNode
|
||||
}
|
||||
|
||||
if (typeof n.questionId !== "string" || n.questionId.length === 0) return null
|
||||
|
||||
return {
|
||||
id,
|
||||
type: "question",
|
||||
questionId: n.questionId,
|
||||
score: typeof n.score === "number" ? n.score : undefined,
|
||||
} satisfies ExamNode
|
||||
})
|
||||
.filter(Boolean) as ExamNode[]
|
||||
}
|
||||
|
||||
if (!Array.isArray(nodes)) return []
|
||||
return normalize(nodes)
|
||||
}
|
||||
|
||||
let initialStructure: ExamNode[] = normalizeStructure(exam.structure)
|
||||
|
||||
if (initialStructure.length === 0 && initialSelected.length > 0) {
|
||||
initialStructure = initialSelected.map(s => ({
|
||||
id: createId(), // Generate stable ID on server
|
||||
type: 'question',
|
||||
initialStructure = initialSelected.map((s) => ({
|
||||
id: createId(),
|
||||
type: "question",
|
||||
questionId: s.id,
|
||||
score: s.score
|
||||
score: s.score,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user