Spaces:
Sleeping
Sleeping
| 'use client'; | |
| import { GeneratedQuestion, QuestionType } from '@/types/quiz'; | |
| import { Card, CardContent, CardHeader } from '@/components/ui/card'; | |
| import { Button } from '@/components/ui/button'; | |
| import { Badge } from '@/components/ui/badge'; | |
| import { cn } from '@/lib/utils'; | |
| export interface QuestionPreviewProps { | |
| question: GeneratedQuestion; | |
| questionType?: QuestionType; | |
| showActions?: boolean; | |
| onEdit?: (question: GeneratedQuestion) => void; | |
| onDuplicate?: (question: GeneratedQuestion) => void; | |
| onRemove?: (questionId: string) => void; | |
| className?: string; | |
| } | |
| export default function QuestionPreview({ | |
| question, | |
| questionType, | |
| showActions = true, | |
| onEdit, | |
| onDuplicate, | |
| onRemove, | |
| className = '', | |
| }: QuestionPreviewProps) { | |
| const renderQuestionContent = () => { | |
| switch (question.type) { | |
| case 'multiple-choice': | |
| return ( | |
| <MultipleChoicePreview | |
| question={question.stem} | |
| options={question.content?.Options || { A: '', B: '', C: '', D: '' }} | |
| correctAnswer={question.content?.Answer || 'A'} | |
| explanation={undefined} | |
| /> | |
| ); | |
| case 'cloze': | |
| return ( | |
| <ClozePreview | |
| question={question.stem} | |
| blanks={[]} | |
| explanation={undefined} | |
| /> | |
| ); | |
| case 'grammar': | |
| return ( | |
| <GrammarPreview | |
| question={question.stem} | |
| errors={[]} | |
| explanation={undefined} | |
| /> | |
| ); | |
| case 'reading-comp': | |
| return ( | |
| <ReadingCompPreview | |
| passage={''} | |
| question={question.stem} | |
| answer={''} | |
| explanation={undefined} | |
| /> | |
| ); | |
| case 'essay': | |
| return ( | |
| <EssayPreview | |
| prompt={question.stem} | |
| guidelines={[]} | |
| rubric={[]} | |
| /> | |
| ); | |
| default: | |
| return ( | |
| <div className="text-gray-600 dark:text-gray-400"> | |
| <p>{question.stem}</p> | |
| </div> | |
| ); | |
| } | |
| }; | |
| return ( | |
| <Card className={cn(className)}> | |
| <CardHeader className="border-b"> | |
| <div className="flex items-start justify-between"> | |
| <div> | |
| <div className="flex items-center space-x-2 mb-1"> | |
| <Badge variant="default"> | |
| {questionType?.name || 'Question'} | |
| </Badge> | |
| <span className="text-xs text-muted-foreground"> | |
| {question.points} points | |
| </span> | |
| </div> | |
| <p className="text-xs text-muted-foreground"> | |
| Created: {new Date(question.createdAt).toLocaleString()} | |
| </p> | |
| </div> | |
| {showActions && ( | |
| <div className="flex items-center space-x-2"> | |
| {onEdit && ( | |
| <Button | |
| onClick={() => onEdit(question)} | |
| variant="outline" | |
| size="sm" | |
| > | |
| Edit | |
| </Button> | |
| )} | |
| {onDuplicate && ( | |
| <Button | |
| onClick={() => onDuplicate(question)} | |
| variant="outline" | |
| size="sm" | |
| className="text-green-600 hover:text-green-700" | |
| > | |
| Duplicate | |
| </Button> | |
| )} | |
| {onRemove && ( | |
| <Button | |
| onClick={() => onRemove(question.id)} | |
| variant="outline" | |
| size="sm" | |
| className="text-destructive hover:text-destructive" | |
| > | |
| Remove | |
| </Button> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| </CardHeader> | |
| {/* Content */} | |
| <CardContent className="p-4"> | |
| {renderQuestionContent()} | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |
| // Individual question type preview components | |
| function MultipleChoicePreview({ question, options, correctAnswer, explanation }: { | |
| question: string; | |
| options: { A: string; B: string; C: string; D: string }; | |
| correctAnswer: string; | |
| explanation?: string; | |
| }) { | |
| return ( | |
| <div className="space-y-4"> | |
| <div> | |
| <h3 className="font-medium text-gray-900 dark:text-white mb-2">Question:</h3> | |
| <p className="text-gray-700 dark:text-gray-300">{question}</p> | |
| </div> | |
| <div> | |
| <h4 className="font-medium text-gray-900 dark:text-white mb-2">Options:</h4> | |
| <div className="space-y-1"> | |
| {Object.entries(options).map(([key, value]) => ( | |
| <div | |
| key={key} | |
| className={`p-2 rounded ${ | |
| key === correctAnswer | |
| ? 'bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-700' | |
| : 'bg-gray-50 dark:bg-gray-700' | |
| }`} | |
| > | |
| <span className="font-medium mr-2">{key}.</span> | |
| {value} | |
| {key === correctAnswer && ( | |
| <span className="ml-2 text-green-600 dark:text-green-400 text-sm">✓ Correct</span> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| {explanation && ( | |
| <div> | |
| <h4 className="font-medium text-gray-900 dark:text-white mb-2">Explanation:</h4> | |
| <p className="text-gray-600 dark:text-gray-400 text-sm">{explanation}</p> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| function ClozePreview({ question, blanks, explanation }: { | |
| question: string; | |
| blanks: { text: string; answer?: string }[]; | |
| explanation?: string; | |
| }) { | |
| return ( | |
| <div className="space-y-4"> | |
| <div> | |
| <h3 className="font-medium text-gray-900 dark:text-white mb-2">Question:</h3> | |
| <p className="text-gray-700 dark:text-gray-300">{question}</p> | |
| </div> | |
| {blanks.length > 0 && ( | |
| <div> | |
| <h4 className="font-medium text-gray-900 dark:text-white mb-2">Answers:</h4> | |
| <div className="space-y-1"> | |
| {blanks.map((blank: { text: string; answer?: string }, index: number) => ( | |
| <div key={index} className="bg-gray-50 dark:bg-gray-700 p-2 rounded"> | |
| <span className="font-medium">Blank {index + 1}:</span> {blank.answer} | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| {explanation && ( | |
| <div> | |
| <h4 className="font-medium text-gray-900 dark:text-white mb-2">Explanation:</h4> | |
| <p className="text-gray-600 dark:text-gray-400 text-sm">{explanation}</p> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| function GrammarPreview({ question, errors, explanation }: { | |
| question: string; | |
| errors: { text: string; correction?: string }[]; | |
| explanation?: string; | |
| }) { | |
| return ( | |
| <div className="space-y-4"> | |
| <div> | |
| <h3 className="font-medium text-gray-900 dark:text-white mb-2">Sentence to Correct:</h3> | |
| <p className="text-gray-700 dark:text-gray-300">{question}</p> | |
| </div> | |
| {errors.length > 0 && ( | |
| <div> | |
| <h4 className="font-medium text-gray-900 dark:text-white mb-2">Errors & Corrections:</h4> | |
| <div className="space-y-2"> | |
| {errors.map((error: { text: string; correction?: string }, index: number) => ( | |
| <div key={index} className="bg-red-50 dark:bg-red-900/20 p-3 rounded border border-red-200 dark:border-red-700"> | |
| <p className="text-sm"><strong>Error:</strong> {error.text}</p> | |
| <p className="text-sm"><strong>Correction:</strong> {error.correction}</p> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| {explanation && ( | |
| <div> | |
| <h4 className="font-medium text-gray-900 dark:text-white mb-2">Explanation:</h4> | |
| <p className="text-gray-600 dark:text-gray-400 text-sm">{explanation}</p> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| function ReadingCompPreview({ passage, question, answer, explanation }: { | |
| passage: string; | |
| question: string; | |
| answer: string; | |
| explanation?: string; | |
| }) { | |
| return ( | |
| <div className="space-y-4"> | |
| <div> | |
| <h3 className="font-medium text-gray-900 dark:text-white mb-2">Reading Passage:</h3> | |
| <div className="bg-gray-50 dark:bg-gray-700 p-3 rounded"> | |
| <p className="text-gray-700 dark:text-gray-300 text-sm leading-relaxed">{passage}</p> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 className="font-medium text-gray-900 dark:text-white mb-2">Question:</h3> | |
| <p className="text-gray-700 dark:text-gray-300">{question}</p> | |
| </div> | |
| {answer && ( | |
| <div> | |
| <h4 className="font-medium text-gray-900 dark:text-white mb-2">Answer:</h4> | |
| <p className="bg-green-50 dark:bg-green-900/20 p-2 rounded text-gray-700 dark:text-gray-300">{answer}</p> | |
| </div> | |
| )} | |
| {explanation && ( | |
| <div> | |
| <h4 className="font-medium text-gray-900 dark:text-white mb-2">Explanation:</h4> | |
| <p className="text-gray-600 dark:text-gray-400 text-sm">{explanation}</p> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| function EssayPreview({ prompt, guidelines, rubric }: { | |
| prompt: string; | |
| guidelines: string[]; | |
| rubric: { criteria: string; points: number }[]; | |
| }) { | |
| return ( | |
| <div className="space-y-4"> | |
| <div> | |
| <h3 className="font-medium text-gray-900 dark:text-white mb-2">Essay Prompt:</h3> | |
| <p className="text-gray-700 dark:text-gray-300">{prompt}</p> | |
| </div> | |
| {guidelines.length > 0 && ( | |
| <div> | |
| <h4 className="font-medium text-gray-900 dark:text-white mb-2">Guidelines:</h4> | |
| <ul className="space-y-1"> | |
| {guidelines.map((guideline: string, index: number) => ( | |
| <li key={index} className="text-gray-600 dark:text-gray-400 text-sm flex items-start"> | |
| <span className="mr-2">•</span> | |
| {guideline} | |
| </li> | |
| ))} | |
| </ul> | |
| </div> | |
| )} | |
| {rubric && rubric.length > 0 && ( | |
| <div> | |
| <h4 className="font-medium text-gray-900 dark:text-white mb-2">Grading Rubric:</h4> | |
| <div className="bg-gray-50 dark:bg-gray-700 p-3 rounded"> | |
| {rubric.map((item, index) => ( | |
| <div key={index} className="flex justify-between items-center mb-1"> | |
| <span className="text-gray-600 dark:text-gray-400 text-sm">{item.criteria}</span> | |
| <span className="text-gray-900 dark:text-white text-sm font-medium">{item.points} points</span> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } |