sto-k-odnomu/admin/src/components/forms/InputButtonsQuestionForm.tsx

252 lines
7.8 KiB
TypeScript
Raw Normal View History

2026-01-06 20:12:36 +00:00
import { useState, useEffect, useRef } from 'react'
import type { InputButtonsQuestion, TestButton } from '@/types/questions'
import { QuestionType } from '@/types/questions'
import { Label } from '@/components/ui/label'
import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea'
import { Button } from '@/components/ui/button'
import { ImageUpload } from '@/components/ui/image-upload'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Plus, Trash2 } from 'lucide-react'
import { Card, CardContent } from '@/components/ui/card'
interface InputButtonsQuestionFormProps {
question: Partial<InputButtonsQuestion>
onChange: (question: Partial<InputButtonsQuestion>) => void
}
export function InputButtonsQuestionForm({
question,
onChange,
}: InputButtonsQuestionFormProps) {
const [word, setWord] = useState(question.word || '')
const [text, setText] = useState(question.text || '')
const [image, setImage] = useState(question.image || '')
const [audio, setAudio] = useState(question.audio || '')
const [buttons, setButtons] = useState<TestButton[]>(question.options || [])
const [answer, setAnswer] = useState(question.answer || '')
const [template, setTemplate] = useState(question.template || '')
const onChangeRef = useRef(onChange)
// Keep onChange ref up to date
useEffect(() => {
onChangeRef.current = onChange
}, [onChange])
// Notify parent of changes
useEffect(() => {
onChangeRef.current({
questionType: QuestionType.INPUT_BUTTONS,
word,
text: text || undefined,
image: image || undefined,
audio: audio || undefined,
options: buttons,
answer,
template,
})
}, [word, text, image, audio, buttons, answer, template])
const addButton = () => {
const newButton: TestButton = {
id: `btn_${Date.now()}`,
text: '',
}
setButtons([...buttons, newButton])
}
const removeButton = (index: number) => {
setButtons(buttons.filter((_, i) => i !== index))
if (answer === buttons[index]?.id) {
setAnswer('')
}
}
const updateButton = (index: number, updates: Partial<TestButton>) => {
const newButtons = [...buttons]
newButtons[index] = { ...newButtons[index], ...updates }
setButtons(newButtons)
}
// Генерация template из answer
const generateTemplate = () => {
if (answer) {
setTemplate(answer.replace(/[^ ]/g, '_'))
}
}
return (
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="word">
Word <span className="text-red-500">*</span>
</Label>
<Input
id="word"
value={word}
onChange={(e) => setWord(e.target.value)}
placeholder="Word or phrase to learn"
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="text">Question Text</Label>
<Textarea
id="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Optional question text"
rows={3}
/>
</div>
<div className="space-y-2">
<ImageUpload
label="Question Image"
value={image}
onChange={(value) => setImage(value || '')}
/>
</div>
<div className="space-y-2">
<Label htmlFor="audio">Audio URL</Label>
<Input
id="audio"
value={audio}
onChange={(e) => setAudio(e.target.value)}
placeholder="https://..."
type="url"
/>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label>
Answer Options <span className="text-red-500">*</span>
</Label>
<Button type="button" variant="outline" size="sm" onClick={addButton}>
<Plus className="h-4 w-4 mr-2" />
Add Button
</Button>
</div>
{buttons.length === 0 ? (
<Card>
<CardContent className="pt-6">
<p className="text-sm text-muted-foreground text-center py-4">
No buttons yet. Add at least one button.
</p>
</CardContent>
</Card>
) : (
<div className="space-y-3">
{buttons.map((button, index) => (
<Card key={button.id}>
<CardContent className="pt-6">
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label className="text-sm font-medium">
Button {index + 1} (ID: {button.id})
</Label>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => removeButton(index)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-2">
<Label htmlFor={`button-text-${index}`}>Text</Label>
<Input
id={`button-text-${index}`}
value={button.text || ''}
onChange={(e) =>
updateButton(index, { text: e.target.value })
}
placeholder="Button text"
/>
</div>
<div className="space-y-2">
<ImageUpload
label="Image"
value={button.image || ''}
onChange={(value) =>
updateButton(index, { image: value || undefined })
}
/>
</div>
</div>
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
<div className="space-y-2">
<Label htmlFor="answer">
Correct Answer <span className="text-red-500">*</span>
</Label>
<Select value={answer} onValueChange={setAnswer} required>
<SelectTrigger id="answer">
<SelectValue placeholder="Select correct answer" />
</SelectTrigger>
<SelectContent>
{buttons.map((button) => (
<SelectItem key={button.id} value={button.id}>
{button.text || button.image || button.id}
</SelectItem>
))}
</SelectContent>
</Select>
{buttons.length === 0 && (
<p className="text-xs text-muted-foreground">
Add at least one button to select an answer
</p>
)}
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor="template">
Template <span className="text-red-500">*</span>
</Label>
{answer && (
<Button
type="button"
variant="outline"
size="sm"
onClick={generateTemplate}
>
Generate from answer
</Button>
)}
</div>
<Input
id="template"
value={template}
onChange={(e) => setTemplate(e.target.value)}
placeholder="e.g., a___e (underscores for letters to input)"
required
/>
<p className="text-xs text-muted-foreground">
Template defines which letters the user needs to input. Use underscores
(_) for letters to input, spaces for word separators.
</p>
</div>
</div>
)
}