| | 'use client'; |
| |
|
| | import { useState } from 'react'; |
| | import { GeneratedQuestion, QuestionType } from '@/types/quiz'; |
| | import QuestionCard from './QuestionCard'; |
| | import { exportQuestionsToDocx } from '@/lib/exportUtils'; |
| | import { Button } from '@/components/ui/button'; |
| | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; |
| | import { Checkbox } from '@/components/ui/checkbox'; |
| | import { Label } from '@/components/ui/label'; |
| | import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; |
| |
|
| | export interface QuestionListProps { |
| | questions: GeneratedQuestion[]; |
| | questionTypes: QuestionType[]; |
| | selectedQuestions?: Set<string>; |
| | onQuestionSelect?: (questionId: string, selected: boolean) => void; |
| | onQuestionEdit?: (question: GeneratedQuestion) => void; |
| | onQuestionDuplicate?: (question: GeneratedQuestion) => void; |
| | onQuestionPreview?: (question: GeneratedQuestion) => void; |
| | onQuestionRemove?: (questionId: string) => void; |
| | onQuestionReorder?: (questions: GeneratedQuestion[]) => void; |
| | editingQuestionId?: string | null; |
| | emptyStateMessage?: string; |
| | emptyStateIcon?: string; |
| | className?: string; |
| | title?: string; |
| | showCount?: boolean; |
| | } |
| |
|
| | export default function QuestionList({ |
| | questions, |
| | questionTypes, |
| | selectedQuestions = new Set(), |
| | onQuestionSelect, |
| | onQuestionEdit, |
| | onQuestionDuplicate, |
| | onQuestionPreview, |
| | onQuestionRemove, |
| | onQuestionReorder, |
| | editingQuestionId = null, |
| | emptyStateMessage = "No questions yet. Start by creating your first question!", |
| | emptyStateIcon = "๐", |
| | className = '', |
| | title = "Question List", |
| | showCount = true, |
| | }: QuestionListProps) { |
| | const [draggedId, setDraggedId] = useState<string | null>(null); |
| | const [isExporting, setIsExporting] = useState(false); |
| | const [includeAnswers, setIncludeAnswers] = useState(true); |
| | const [exportFormat, setExportFormat] = useState<'docx' | 'txt'>('docx'); |
| |
|
| | const handleSelectAll = () => { |
| | if (onQuestionSelect) { |
| | const allSelected = questions.length > 0 && questions.every(q => selectedQuestions.has(q.id)); |
| | questions.forEach(question => { |
| | onQuestionSelect(question.id, !allSelected); |
| | }); |
| | } |
| | }; |
| |
|
| | const handleDragStart = (questionId: string) => { |
| | setDraggedId(questionId); |
| | }; |
| |
|
| | const handleDragEnd = () => { |
| | setDraggedId(null); |
| | }; |
| |
|
| | const handleDrop = (targetId: string) => { |
| | if (!draggedId || !onQuestionReorder || draggedId === targetId) return; |
| |
|
| | const newQuestions = [...questions]; |
| | const draggedIndex = newQuestions.findIndex(q => q.id === draggedId); |
| | const targetIndex = newQuestions.findIndex(q => q.id === targetId); |
| |
|
| | if (draggedIndex !== -1 && targetIndex !== -1) { |
| | |
| | const [draggedItem] = newQuestions.splice(draggedIndex, 1); |
| | newQuestions.splice(targetIndex, 0, draggedItem); |
| | onQuestionReorder(newQuestions); |
| | } |
| | }; |
| |
|
| | const allSelected = questions.length > 0 && questions.every(q => selectedQuestions.has(q.id)); |
| | const someSelected = questions.some(q => selectedQuestions.has(q.id)); |
| |
|
| | return ( |
| | <Card className={className}> |
| | <CardHeader className="border-b"> |
| | <div className="flex items-center justify-between"> |
| | <CardTitle className="text-lg font-semibold"> |
| | ๐๏ธ {title} {showCount && `(${questions.length})`} |
| | </CardTitle> |
| | |
| | {/* Export and Bulk Actions */} |
| | {questions.length > 0 && ( |
| | <div className="flex items-center space-x-3"> |
| | {/* Export Options */} |
| | <div className="flex items-center space-x-3"> |
| | {/* Format Selector */} |
| | <Select value={exportFormat} onValueChange={(value) => setExportFormat(value as 'docx' | 'txt')}> |
| | <SelectTrigger className="w-[100px]"> |
| | <SelectValue /> |
| | </SelectTrigger> |
| | <SelectContent> |
| | <SelectItem value="docx">DOCX</SelectItem> |
| | <SelectItem value="txt">TXT</SelectItem> |
| | </SelectContent> |
| | </Select> |
| | |
| | {/* Include Answers Toggle */} |
| | <div className="flex items-center space-x-2"> |
| | <Checkbox |
| | id="include-answers" |
| | checked={includeAnswers} |
| | onCheckedChange={(checked) => setIncludeAnswers(checked as boolean)} |
| | /> |
| | <Label htmlFor="include-answers" className="text-sm cursor-pointer"> |
| | Include Answers |
| | </Label> |
| | </div> |
| | |
| | {/* Export Button */} |
| | <Button |
| | onClick={async () => { |
| | if (isExporting) return; |
| | |
| | setIsExporting(true); |
| | try { |
| | const questionsToExport = someSelected && selectedQuestions.size > 0 |
| | ? questions.filter(q => selectedQuestions.has(q.id)) |
| | : questions; |
| | const filename = someSelected && selectedQuestions.size > 0 |
| | ? `selected-questions-${selectedQuestions.size}${includeAnswers ? '-with-answers' : '-no-answers'}.${exportFormat}` |
| | : `all-questions-${questions.length}${includeAnswers ? '-with-answers' : '-no-answers'}.${exportFormat}`; |
| | |
| | // Debug: Log the first question structure |
| | if (questionsToExport.length > 0) { |
| | console.log('First question structure:', questionsToExport[0]); |
| | console.log('First question content:', questionsToExport[0].content); |
| | } |
| | |
| | if (exportFormat === 'docx') { |
| | await exportQuestionsToDocx(questionsToExport, filename, includeAnswers); |
| | } else { |
| | // For now, just show a message that TXT export is not implemented |
| | alert('TXT export is not yet implemented. Please use DOCX format.'); |
| | } |
| | } catch (error) { |
| | console.error('Export failed:', error); |
| | // You could add a toast notification here |
| | } finally { |
| | setIsExporting(false); |
| | } |
| | }} |
| | disabled={isExporting} |
| | variant="default" |
| | size="sm" |
| | title={someSelected && selectedQuestions.size > 0 |
| | ? `Export ${selectedQuestions.size} selected questions to ${exportFormat.toUpperCase()} file` |
| | : `Export all questions to ${exportFormat.toUpperCase()} file` |
| | } |
| | > |
| | <span>{isExporting ? 'โณ' : '๐'}</span> |
| | <span> |
| | {isExporting |
| | ? 'Exporting...' |
| | : someSelected && selectedQuestions.size > 0 |
| | ? `Export Selected (${selectedQuestions.size})` |
| | : `Export to ${exportFormat.toUpperCase()}` |
| | } |
| | </span> |
| | </Button> |
| | </div> |
| | |
| | {/* Bulk Actions */} |
| | {onQuestionSelect && ( |
| | <div className="flex items-center space-x-2"> |
| | <Button |
| | onClick={handleSelectAll} |
| | variant="ghost" |
| | size="sm" |
| | > |
| | {allSelected ? 'Deselect All' : 'Select All'} |
| | </Button> |
| | |
| | {someSelected && ( |
| | <span className="text-xs text-primary"> |
| | {selectedQuestions.size} selected |
| | </span> |
| | )} |
| | </div> |
| | )} |
| | </div> |
| | )} |
| | </div> |
| | </CardHeader> |
| | |
| | {/* Content */} |
| | <CardContent className="h-[calc(100%-80px)] overflow-y-auto p-4"> |
| | {questions.length === 0 ? ( |
| | <EmptyState message={emptyStateMessage} icon={emptyStateIcon} /> |
| | ) : ( |
| | <div className="space-y-3"> |
| | {questions.map((question) => ( |
| | <QuestionCard |
| | key={question.id} |
| | question={question} |
| | questionTypes={questionTypes} |
| | isSelected={selectedQuestions.has(question.id)} |
| | onSelect={onQuestionSelect} |
| | onEdit={onQuestionEdit} |
| | onDuplicate={onQuestionDuplicate} |
| | onPreview={onQuestionPreview} |
| | onRemove={onQuestionRemove} |
| | onDragStart={handleDragStart} |
| | onDragEnd={handleDragEnd} |
| | onDrop={handleDrop} |
| | isDragging={draggedId === question.id} |
| | isEditing={editingQuestionId === question.id} |
| | /> |
| | ))} |
| | </div> |
| | )} |
| | </CardContent> |
| | </Card> |
| | ); |
| | } |
| |
|
| | |
| | interface EmptyStateProps { |
| | message: string; |
| | icon: string; |
| | } |
| |
|
| | function EmptyState({ message, icon }: EmptyStateProps) { |
| | return ( |
| | <div className="text-center text-gray-500 dark:text-gray-400 mt-8"> |
| | <div className="text-4xl mb-4">{icon}</div> |
| | <p className="text-sm leading-relaxed max-w-md mx-auto">{message}</p> |
| | </div> |
| | ); |
| | } |