diff --git a/src/components/content-management/PracticeQuestionEditorFields.tsx b/src/components/content-management/PracticeQuestionEditorFields.tsx new file mode 100644 index 0000000..56042d6 --- /dev/null +++ b/src/components/content-management/PracticeQuestionEditorFields.tsx @@ -0,0 +1,348 @@ +import { Check, Plus, X } from "lucide-react" +import { Input } from "../ui/input" +import { Select } from "../ui/select" +import { cn } from "../../lib/utils" + +export type PracticeQuestionEditorType = "MCQ" | "TRUE_FALSE" | "SHORT" | "AUDIO" +export type PracticeQuestionEditorDifficulty = "EASY" | "MEDIUM" | "HARD" + +export interface PracticeQuestionOptionDraft { + text: string + isCorrect: boolean +} + +export interface PracticeQuestionEditorValue { + questionText: string + questionType: PracticeQuestionEditorType + difficultyLevel: PracticeQuestionEditorDifficulty + points: number + tips: string + explanation: string + options: PracticeQuestionOptionDraft[] + voicePrompt: string + sampleAnswerVoicePrompt: string + audioCorrectAnswerText: string + shortAnswer: string +} + +export function createEmptyPracticeQuestionDraft(): PracticeQuestionEditorValue { + return { + questionText: "", + questionType: "MCQ", + difficultyLevel: "EASY", + points: 1, + tips: "", + explanation: "", + options: [ + { text: "", isCorrect: true }, + { text: "", isCorrect: false }, + { text: "", isCorrect: false }, + { text: "", isCorrect: false }, + ], + voicePrompt: "", + sampleAnswerVoicePrompt: "", + audioCorrectAnswerText: "", + shortAnswer: "", + } +} + +function defaultOptionsForType( + type: PracticeQuestionEditorType, + previousType: PracticeQuestionEditorType, + current: PracticeQuestionOptionDraft[], +): PracticeQuestionOptionDraft[] { + if (type === "TRUE_FALSE") { + if (previousType === "TRUE_FALSE" && current.length >= 2) { + return current.map((o, i) => ({ + text: i === 0 ? "True" : "False", + isCorrect: o.isCorrect, + })) + } + return [ + { text: "True", isCorrect: true }, + { text: "False", isCorrect: false }, + ] + } + if (type === "MCQ") { + if (previousType === "MCQ" && current.length >= 2) { + const hasCorrect = current.some((o) => o.isCorrect) + return current.map((o, i) => ({ + text: o.text, + isCorrect: hasCorrect ? o.isCorrect : i === 0, + })) + } + return [ + { text: "", isCorrect: true }, + { text: "", isCorrect: false }, + { text: "", isCorrect: false }, + { text: "", isCorrect: false }, + ] + } + return current +} + +export type PracticeQuestionFieldErrorKey = + | "questionText" + | "points" + | "shortAnswer" + | "options" + | "correctOption" + +export interface PracticeQuestionEditorFieldsProps { + value: PracticeQuestionEditorValue + onChange: (next: PracticeQuestionEditorValue) => void + fieldErrors?: Partial> + showFieldErrors?: boolean +} + +export function PracticeQuestionEditorFields({ + value, + onChange, + fieldErrors = {}, + showFieldErrors = false, +}: PracticeQuestionEditorFieldsProps) { + const patch = (partial: Partial) => { + onChange({ ...value, ...partial }) + } + + const setType = (questionType: PracticeQuestionEditorType) => { + const options = defaultOptionsForType(questionType, value.questionType, value.options) + onChange({ ...value, questionType, options }) + } + + const updateOption = (optionIndex: number, updates: Partial) => { + const options = value.options.map((opt, i) => (i === optionIndex ? { ...opt, ...updates } : opt)) + onChange({ ...value, options }) + } + + const addOption = () => { + onChange({ ...value, options: [...value.options, { text: "", isCorrect: false }] }) + } + + const removeOption = (optionIndex: number) => { + if (value.options.length <= 2) return + const options = value.options.filter((_, i) => i !== optionIndex) + if (!options.some((o) => o.isCorrect) && options.length > 0) { + options[0] = { ...options[0], isCorrect: true } + } + onChange({ ...value, options }) + } + + const setCorrectOption = (optionIndex: number) => { + const options = value.options.map((opt, i) => ({ ...opt, isCorrect: i === optionIndex })) + onChange({ ...value, options }) + } + + return ( +
+
+ +