| | '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); |
| |
|
| | |
| | useEffect(() => { |
| | if (question) { |
| | setEditedQuestion({ ...question }); |
| | } |
| | }, [question]); |
| |
|
| | const handleSave = async () => { |
| | if (!editedQuestion) return; |
| | |
| | |
| | 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 { |
| | |
| | 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> |
| | ); |
| | } |
| |
|