import InputError from '@/components/input-error'; import LoadingButton from '@/components/loading-button'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { onHandleChange } from '@/lib/inertia'; import { useForm } from '@inertiajs/react'; import { useEffect, useState } from 'react'; import { Editor } from 'richtor'; import 'richtor/styles'; import FillBlankForm from './question-types/fill-blank-form'; import ListeningForm from './question-types/listening-form'; import MatchingForm from './question-types/matching-form'; import MultipleChoiceForm from './question-types/multiple-choice-form'; import OrderingForm from './question-types/ordering-form'; import ShortAnswerForm from './question-types/short-answer-form'; interface Props { exam: Exam; question?: ExamQuestion; handler: React.ReactNode; } type QuestionFormData = { exam_id: number | string; title: string; description: string; marks: number; options: { answers?: string[]; matches?: Array<{ id: number; question: string; answer: string }>; items?: string[]; correct_order?: number[]; sample_answer?: string; audio_url?: string; audio_file?: File; audio_source?: 'url' | 'upload'; instructions?: string; [key: string]: any; }; question_options: Array<{ id?: number; option_text: string; is_correct: boolean; sort: number; }>; question_type: ExamQuestionType; exam_question_id: number | null; }; const questionTypes: { value: ExamQuestionType; label: string }[] = [ { value: 'multiple_choice', label: 'Multiple Choice' }, { value: 'multiple_select', label: 'Multiple Select' }, { value: 'matching', label: 'Matching' }, { value: 'fill_blank', label: 'Fill in the Blank' }, { value: 'ordering', label: 'Ordering' }, { value: 'short_answer', label: 'Short Answer' }, { value: 'listening', label: 'Listening' }, ]; const QuestionDialog = ({ exam, question, handler }: Props) => { const [open, setOpen] = useState(false); const [isSubmit, setIsSubmit] = useState(false); const [isFileSelected, setIsFileSelected] = useState(false); const [isFileUploaded, setIsFileUploaded] = useState(false); const initialFormData: QuestionFormData = { exam_id: exam.id, title: question?.title || '', description: question?.description || '', marks: question?.marks || 1, options: question?.options || {}, question_options: (question?.question_options || []).map((opt) => ({ id: opt.id ? Number(opt.id) : undefined, option_text: opt.option_text, is_correct: opt.is_correct, sort: opt.sort, })), question_type: question?.question_type || 'multiple_choice', exam_question_id: question?.id ? Number(question.id) : null, }; const { data, setData, post, put, errors, processing, reset, clearErrors } = useForm(initialFormData); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); // For listening questions with file upload if (data.question_type === 'listening' && data.options?.audio_source === 'upload') { // Only wait for upload if it's a NEW question or if a new file was selected if (!question || isFileSelected) { if (!isFileUploaded) { setIsSubmit(true); return; } } } // Otherwise submit the form immediately submitForm(); }; const submitForm = () => { clearErrors(); if (question) { put(route('exam-questions.update', question.id), { preserveScroll: true, onSuccess: () => { setOpen(false); reset(); setIsSubmit(false); setIsFileUploaded(false); }, }); } else { post(route('exam-questions.store'), { preserveScroll: true, onSuccess: () => { setOpen(false); reset(); setIsSubmit(false); setIsFileUploaded(false); }, }); } }; // Auto-submit after file upload completes useEffect(() => { if (isFileUploaded && isSubmit) { submitForm(); } }, [isFileUploaded, isSubmit]); // Reset upload states when dialog opens/closes useEffect(() => { if (!open) { setIsSubmit(false); setIsFileSelected(false); setIsFileUploaded(false); } }, [open]); const renderQuestionTypeForm = () => { const props = { data, setData, errors, }; switch (data.question_type) { case 'multiple_choice': case 'multiple_select': return ; case 'matching': return ; case 'fill_blank': return ; case 'ordering': return ; case 'short_answer': return ; case 'listening': return ( ); default: return null; } }; return ( {handler} {question ? 'Edit Question' : 'Create Question'} Question Type * setData('question_type', value)} disabled={question ? true : false} > {questionTypes.map((type) => ( {type.label} ))} Marks * setData('marks', parseFloat(e.target.value) || 0)} placeholder="Enter marks" /> Question Title * onHandleChange(e, setData)} placeholder="Enter question title" /> Description (Optional) setData((prev) => ({ ...prev, description: value as string, })) } /> {renderQuestionTypeForm()} setOpen(false)} disabled={processing || isSubmit}> Cancel {question ? 'Update Question' : 'Create Question'} ); }; export default QuestionDialog;