| | '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> |
| | ); |
| | } |
| |
|
| | |
| | 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> |
| | ); |
| | } |