QuizFlash / src /components /question-output /QuestionEditModal.tsx
Shih-hungg's picture
string
8cd242f
'use client';
import { useState, useEffect } from 'react';
import { GeneratedQuestion, QuestionType } from '@/types/quiz';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
export interface QuestionEditModalProps {
question: GeneratedQuestion | null;
questionTypes: QuestionType[];
isOpen: boolean;
onClose: () => void;
onSave: (updatedQuestion: GeneratedQuestion) => void;
}
export default function QuestionEditModal({
question,
questionTypes,
isOpen,
onClose,
onSave,
}: QuestionEditModalProps) {
const [editedQuestion, setEditedQuestion] = useState<GeneratedQuestion | null>(null);
const [isSaving, setIsSaving] = useState(false);
// Initialize form when question changes
useEffect(() => {
if (question) {
setEditedQuestion({ ...question });
}
}, [question]);
const handleSave = async () => {
if (!editedQuestion) return;
// Basic validation
if (!editedQuestion.stem.trim()) {
alert('Please enter a question');
return;
}
if (!editedQuestion.content.Options.A.trim() ||
!editedQuestion.content.Options.B.trim() ||
!editedQuestion.content.Options.C.trim() ||
!editedQuestion.content.Options.D.trim()) {
alert('Please fill in all answer options');
return;
}
setIsSaving(true);
try {
// Call the update API
const response = await fetch('/api/update-question', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(editedQuestion),
});
if (response.ok) {
const updatedQuestion = await response.json();
onSave(updatedQuestion);
onClose();
} else {
const errorData = await response.json();
alert(`Failed to update question: ${errorData.error || 'Unknown error'}`);
}
} catch (error) {
console.error('Error updating question:', error);
alert('Failed to update question. Please try again.');
} finally {
setIsSaving(false);
}
};
const handleCancel = () => {
setEditedQuestion(question ? { ...question } : null);
onClose();
};
const updateQuestionField = (field: string, value: string | number | boolean) => {
if (!editedQuestion) return;
setEditedQuestion(prev => {
if (!prev) return null;
if (field === 'stem') {
return { ...prev, stem: String(value) };
} else if (field.startsWith('content.Options.')) {
const optionKey = field.replace('content.Options.', '');
return {
...prev,
content: {
...prev.content,
Options: {
...prev.content.Options,
[optionKey]: String(value)
}
}
};
} else if (field.startsWith('content.')) {
const contentField = field.replace('content.', '');
return {
...prev,
content: {
...prev.content,
[contentField]: String(value)
}
};
}
return prev;
});
};
if (!editedQuestion) return null;
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Edit Question</DialogTitle>
</DialogHeader>
<div className="space-y-6 py-4">
{/* Question Type - Read Only */}
<div className="space-y-2">
<Label htmlFor="question-type">Question Type</Label>
<div className="flex items-center space-x-2 p-3 bg-gray-50 dark:bg-gray-800 rounded-md border w-full">
<span className="text-lg flex-shrink-0">
{questionTypes.find(t => t.id === editedQuestion.type)?.icon || '📝'}
</span>
<span className="text-sm font-medium break-words">
{questionTypes.find(t => t.id === editedQuestion.type)?.name || 'Unknown Type'}
</span>
</div>
</div>
{/* Points - Read Only */}
<div className="space-y-2">
<Label htmlFor="question-points">Points</Label>
<div className="flex items-center space-x-2 p-3 bg-gray-50 dark:bg-gray-800 rounded-md border w-20">
<span className="text-sm font-medium">{editedQuestion.points} pts</span>
</div>
</div>
{/* Question Stem */}
<div className="space-y-2">
<Label htmlFor="question-stem">Question</Label>
<Textarea
id="question-stem"
value={editedQuestion.stem}
onChange={(e) => updateQuestionField('stem', e.target.value)}
placeholder="Enter your question here..."
className="min-h-[120px] w-full resize-y"
rows={4}
/>
</div>
{/* Options */}
<div className="space-y-4">
<Label>Options</Label>
<div className="grid grid-cols-1 gap-4">
{Object.entries(editedQuestion.content.Options).map(([key, value]) => (
<div key={key} className="flex items-start space-x-3">
<Label className="w-8 text-sm font-medium mt-2">{key})</Label>
<Textarea
value={value}
onChange={(e) => updateQuestionField(`content.Options.${key}`, e.target.value)}
placeholder={`Option ${key}`}
className="flex-1 min-h-[40px] resize-y"
rows={2}
/>
</div>
))}
</div>
</div>
{/* Correct Answer */}
<div className="space-y-2">
<Label htmlFor="correct-answer">Correct Answer</Label>
<Select
value={editedQuestion.content.Answer}
onValueChange={(value) => updateQuestionField('content.Answer', value)}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select correct answer" />
</SelectTrigger>
<SelectContent className="max-w-full">
{Object.keys(editedQuestion.content.Options).map((key) => (
<SelectItem key={key} value={key} className="whitespace-normal">
<span className="font-medium">{key})</span> {editedQuestion.content.Options[key as keyof typeof editedQuestion.content.Options]}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={handleCancel}
disabled={isSaving}
>
Cancel
</Button>
<Button
onClick={handleSave}
disabled={isSaving}
>
{isSaving ? 'Saving...' : 'Save Changes'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}