import { useState, useRef } from "react" import { GripVertical, Plus, Trash2, ChevronDown, } from "lucide-react" import { FormInput } from "@/components/shared/form-input" import type { QuestionType, CreateQuestion } from "@/lib/types" const QUESTION_TYPES: { value: QuestionType; label: string }[] = [ { value: "short_text", label: "Short Text" }, { value: "long_text", label: "Long Text" }, { value: "multiple_choice", label: "Multiple Choice" }, { value: "checkbox", label: "Checkbox" }, { value: "dropdown", label: "Dropdown" }, { value: "date", label: "Date" }, { value: "rating", label: "Rating" }, ] const TYPES_WITH_OPTIONS: QuestionType[] = ["multiple_choice", "checkbox", "dropdown"] interface QuestionEditorProps { questions: CreateQuestion[] onChange: (questions: CreateQuestion[]) => void } function reposition(questions: CreateQuestion[]): CreateQuestion[] { return questions.map((q, i) => ({ ...q, position: i + 1 })) } export function QuestionEditor({ questions, onChange }: QuestionEditorProps) { const [dragIndex, setDragIndex] = useState(null) const [dragOverIndex, setDragOverIndex] = useState(null) const dragNode = useRef(null) function updateQuestion(index: number, updates: Partial) { const updated = questions.map((q, i) => { if (i !== index) return q const merged = { ...q, ...updates } if (updates.type && !TYPES_WITH_OPTIONS.includes(merged.type)) { merged.options = [] } if ( updates.type && TYPES_WITH_OPTIONS.includes(merged.type) && merged.options.length === 0 ) { merged.options = [{ label: "", position: 1 }] } return merged }) onChange(updated) } function addQuestion() { onChange([ ...questions, { type: "short_text", title: "", required: false, position: questions.length + 1, options: [], }, ]) } function removeQuestion(index: number) { if (questions.length <= 1) return onChange(reposition(questions.filter((_, i) => i !== index))) } function updateOption(qIndex: number, oIndex: number, label: string) { const updated = questions.map((q, i) => { if (i !== qIndex) return q const options = q.options.map((o, j) => j === oIndex ? { ...o, label } : o ) return { ...q, options } }) onChange(updated) } function addOption(qIndex: number) { const updated = questions.map((q, i) => { if (i !== qIndex) return q return { ...q, options: [...q.options, { label: "", position: q.options.length + 1 }], } }) onChange(updated) } function removeOption(qIndex: number, oIndex: number) { const updated = questions.map((q, i) => { if (i !== qIndex) return q const options = q.options .filter((_, j) => j !== oIndex) .map((o, j) => ({ ...o, position: j + 1 })) return { ...q, options } }) onChange(updated) } function handleDragStart(index: number, e: React.DragEvent) { setDragIndex(index) dragNode.current = e.currentTarget e.dataTransfer.effectAllowed = "move" requestAnimationFrame(() => { if (dragNode.current) { dragNode.current.style.opacity = "0.4" } }) } function handleDragOver(index: number, e: React.DragEvent) { e.preventDefault() e.dataTransfer.dropEffect = "move" if (dragIndex === null || dragIndex === index) return setDragOverIndex(index) } function handleDragEnd() { if (dragNode.current) { dragNode.current.style.opacity = "1" } if (dragIndex !== null && dragOverIndex !== null && dragIndex !== dragOverIndex) { const reordered = [...questions] const [moved] = reordered.splice(dragIndex, 1) reordered.splice(dragOverIndex, 0, moved) onChange(reposition(reordered)) } setDragIndex(null) setDragOverIndex(null) dragNode.current = null } return ( <>
{questions.map((question, qIndex) => (
handleDragStart(qIndex, e)} onDragOver={(e) => handleDragOver(qIndex, e)} onDragEnd={handleDragEnd} onDragLeave={() => setDragOverIndex(null)} className={`rounded-xl border bg-card p-5 shadow-sm transition-all ${ dragOverIndex === qIndex && dragIndex !== qIndex ? "border-primary ring-2 ring-primary/20" : "border-border" }`} >
{qIndex + 1}
{questions.length > 1 && ( )}
updateQuestion(qIndex, { title: e.target.value }) } required />
{TYPES_WITH_OPTIONS.includes(question.type) && (
{question.options.map((option, oIndex) => (
{oIndex + 1}. updateOption(qIndex, oIndex, e.target.value) } className="flex-1 rounded-lg border border-input bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground outline-none transition-colors focus:border-primary focus:ring-2 focus:ring-primary/20" /> {question.options.length > 1 && ( )}
))}
)}
))}
) }