Files
SerpentRace/SerpentRace_Frontend/src/components/DeckCreator/TaskCardEditor.jsx
T

500 lines
22 KiB
React
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// src/components/DeckCreator/TaskCardEditor.jsx
// Feladat kártya szerkesztő (Quiz, Igaz/Hamis, Párosítás, Szöveges)
import React from "react"
import { FaPlus, FaTrash, FaCheck, FaTimes } from "react-icons/fa"
const taskSubTypes = [
{ value: 'quiz', label: '📋 Quiz (A/B/C/D)', description: 'Feleletválasztós kérdés' },
{ value: 'truefalse', label: '✅ Igaz/Hamis', description: 'Igaz vagy hamis állítás' },
{ value: 'matching', label: '🔗 Párosítás', description: 'Elemek összekapcsolása' },
{ value: 'text', label: '✏️ Szöveges válasz', description: 'Szabadszöveges válasz' }
]
const timeLimits = [
{ value: 15, label: '15 másodperc' },
{ value: 30, label: '30 másodperc' },
{ value: 45, label: '45 másodperc' },
{ value: 60, label: '1 perc' },
{ value: 90, label: '1.5 perc' },
{ value: 120, label: '2 perc' }
]
export default function TaskCardEditor({ card, onChange }) {
const updateField = (field, value) => {
onChange({ [field]: value })
}
const updateOption = (index, value) => {
const newOptions = [...card.options]
newOptions[index] = value
onChange({ options: newOptions })
}
const addMatchingPair = () => {
const newLeft = [...(card.leftItems || []), '']
const newRight = [...(card.rightItems || []), '']
const newCorrectPairs = { ...(card.correctPairs || {}), [newLeft.length - 1]: newRight.length - 1 }
onChange({
leftItems: newLeft,
rightItems: newRight,
correctPairs: newCorrectPairs
})
}
const removeMatchingPair = (index) => {
const newLeft = card.leftItems.filter((_, i) => i !== index)
const newRight = card.rightItems.filter((_, i) => i !== index)
const newCorrectPairs = {}
// Újraszámozás
Object.entries(card.correctPairs).forEach(([leftIdx, rightIdx]) => {
const newLeftIdx = parseInt(leftIdx) > index ? parseInt(leftIdx) - 1 : parseInt(leftIdx)
const newRightIdx = parseInt(rightIdx) > index ? parseInt(rightIdx) - 1 : parseInt(rightIdx)
if (newLeftIdx !== index && newRightIdx !== index) {
newCorrectPairs[newLeftIdx] = newRightIdx
}
})
onChange({
leftItems: newLeft,
rightItems: newRight,
correctPairs: newCorrectPairs
})
}
const addAcceptedAnswer = () => {
const newAnswers = [...(card.acceptedAnswers || []), '']
onChange({ acceptedAnswers: newAnswers })
}
const updateAcceptedAnswer = (index, value) => {
const newAnswers = [...card.acceptedAnswers]
newAnswers[index] = value
onChange({ acceptedAnswers: newAnswers })
}
const removeAcceptedAnswer = (index) => {
const newAnswers = card.acceptedAnswers.filter((_, i) => i !== index)
onChange({ acceptedAnswers: newAnswers })
}
return (
<div className="max-w-4xl mx-auto space-y-6">
{/* Feladat típus választó */}
<div className="bg-[color:var(--color-surface)] rounded-xl p-6">
<h3 className="text-lg font-semibold text-[color:var(--color-text)] mb-4">
🎯 Feladat típusa
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{taskSubTypes.map(type => (
<button
key={type.value}
onClick={() => updateField('subType', type.value)}
className={`
p-4 rounded-xl border text-left transition-all duration-200 hover:scale-105
${card.subType === type.value
? 'bg-[color:var(--color-success)]/10 border-[color:var(--color-success)] shadow-lg'
: 'bg-[color:var(--color-background)] border-[color:var(--color-surface-selected)] hover:bg-[color:var(--color-surface-selected)]'
}
`}
>
<div className="font-semibold text-[color:var(--color-text)]">
{type.label}
</div>
<div className="text-sm text-[color:var(--color-text-muted)] mt-1">
{type.description}
</div>
</button>
))}
</div>
</div>
{/* Quiz típus szerkesztő */}
{card.subType === 'quiz' && (
<div className="bg-[color:var(--color-surface)] rounded-xl p-6">
<h3 className="text-lg font-semibold text-[color:var(--color-text)] mb-4">
📋 Quiz kérdés
</h3>
{/* Kérdés */}
<div className="mb-6">
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
Kérdés
</label>
<textarea
value={card.question || ''}
onChange={(e) => updateField('question', e.target.value)}
className="w-full px-4 py-3 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200 resize-none"
rows="3"
placeholder="Írd be a kérdést..."
/>
</div>
{/* Válaszlehetőségek */}
<div className="space-y-3">
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium">
Válaszlehetőségek
</label>
{['A', 'B', 'C', 'D'].map((letter, index) => (
<div key={index} className="flex items-center gap-3">
<div className="flex items-center gap-2">
<button
onClick={() => updateField('correctAnswer', index)}
className={`
w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm transition-all duration-200
${card.correctAnswer === index
? 'bg-[color:var(--color-success)] text-[color:var(--color-text-inverse)]'
: 'bg-[color:var(--color-background)] text-[color:var(--color-text)] border border-[color:var(--color-surface-selected)]'
}
`}
>
{letter}
</button>
{card.correctAnswer === index && (
<FaCheck className="text-[color:var(--color-success)] text-sm" />
)}
</div>
<input
type="text"
value={card.options?.[index] || ''}
onChange={(e) => updateOption(index, e.target.value)}
className="flex-1 px-4 py-2 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200"
placeholder={`${letter} válasz...`}
/>
</div>
))}
</div>
</div>
)}
{/* Igaz/Hamis típus szerkesztő */}
{card.subType === 'truefalse' && (
<div className="bg-[color:var(--color-surface)] rounded-xl p-6">
<h3 className="text-lg font-semibold text-[color:var(--color-text)] mb-4">
Igaz/Hamis állítás
</h3>
{/* Állítás */}
<div className="mb-6">
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
Állítás
</label>
<textarea
value={card.statement || ''}
onChange={(e) => updateField('statement', e.target.value)}
className="w-full px-4 py-3 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200 resize-none"
rows="3"
placeholder="Írd be az állítást..."
/>
</div>
{/* Helyes válasz */}
<div>
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-3">
Helyes válasz
</label>
<div className="flex gap-4">
<button
onClick={() => updateField('isTrue', true)}
className={`
flex items-center gap-3 px-6 py-3 rounded-xl font-medium transition-all duration-200 hover:scale-105
${card.isTrue === true
? 'bg-[color:var(--color-success)] text-[color:var(--color-text-inverse)] shadow-lg'
: 'bg-[color:var(--color-background)] text-[color:var(--color-text)] border border-[color:var(--color-surface-selected)]'
}
`}
>
<FaCheck />
IGAZ
</button>
<button
onClick={() => updateField('isTrue', false)}
className={`
flex items-center gap-3 px-6 py-3 rounded-xl font-medium transition-all duration-200 hover:scale-105
${card.isTrue === false
? 'bg-[color:var(--color-error)] text-[color:var(--color-text-inverse)] shadow-lg'
: 'bg-[color:var(--color-background)] text-[color:var(--color-text)] border border-[color:var(--color-surface-selected)]'
}
`}
>
<FaTimes />
HAMIS
</button>
</div>
</div>
</div>
)}
{/* Párosítás típus szerkesztő */}
{card.subType === 'matching' && (
<div className="bg-[color:var(--color-surface)] rounded-xl p-6">
<h3 className="text-lg font-semibold text-[color:var(--color-text)] mb-4">
🔗 Párosítás feladat
</h3>
{/* Feladat leírás */}
<div className="mb-6">
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
Feladat leírása
</label>
<input
type="text"
value={card.taskDescription || ''}
onChange={(e) => updateField('taskDescription', e.target.value)}
className="w-full px-4 py-2 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200"
placeholder="Pl.: Párosítsd a országokat a fővárosukkal"
/>
</div>
{/* Párosítások */}
<div className="space-y-4">
<div className="flex justify-between items-center">
<label className="text-[color:var(--color-text-muted)] text-sm font-medium">
Párosítások
</label>
<button
onClick={addMatchingPair}
className="flex items-center gap-2 px-3 py-1 rounded-lg bg-[color:var(--color-success)] text-[color:var(--color-text-inverse)] text-sm font-medium hover:bg-[color:var(--color-success)]/80 transition-all duration-200"
>
<FaPlus />
Új pár
</button>
</div>
{(card.leftItems || []).map((leftItem, index) => (
<div key={index} className="grid grid-cols-5 gap-3 items-center">
<input
type="text"
value={leftItem}
onChange={(e) => {
const newLeft = [...card.leftItems]
newLeft[index] = e.target.value
onChange({ leftItems: newLeft })
}}
className="col-span-2 px-3 py-2 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200 text-sm"
placeholder="Bal oldal..."
/>
<div className="text-center text-[color:var(--color-text-muted)]"></div>
<input
type="text"
value={card.rightItems?.[index] || ''}
onChange={(e) => {
const newRight = [...(card.rightItems || [])]
newRight[index] = e.target.value
onChange({ rightItems: newRight })
}}
className="px-3 py-2 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200 text-sm"
placeholder="Jobb oldal..."
/>
<button
onClick={() => removeMatchingPair(index)}
className="p-2 rounded-lg bg-[color:var(--color-error)]/10 text-[color:var(--color-error)] hover:bg-[color:var(--color-error)]/20 transition-all duration-200"
>
<FaTrash className="text-xs" />
</button>
</div>
))}
</div>
</div>
)}
{/* Szöveges válasz típus szerkesztő */}
{card.subType === 'text' && (
<div className="bg-[color:var(--color-surface)] rounded-xl p-6">
<h3 className="text-lg font-semibold text-[color:var(--color-text)] mb-4">
Szöveges válasz
</h3>
{/* Kérdés */}
<div className="mb-6">
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
Kérdés
</label>
<textarea
value={card.question || ''}
onChange={(e) => updateField('question', e.target.value)}
className="w-full px-4 py-3 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200 resize-none"
rows="3"
placeholder="Írd be a kérdést..."
/>
</div>
{/* Elfogadott válaszok */}
<div className="mb-6">
<div className="flex justify-between items-center mb-3">
<label className="text-[color:var(--color-text-muted)] text-sm font-medium">
Elfogadott válaszok
</label>
<button
onClick={addAcceptedAnswer}
className="flex items-center gap-2 px-3 py-1 rounded-lg bg-[color:var(--color-success)] text-[color:var(--color-text-inverse)] text-sm font-medium hover:bg-[color:var(--color-success)]/80 transition-all duration-200"
>
<FaPlus />
Új válasz
</button>
</div>
<div className="space-y-2">
{(card.acceptedAnswers || ['', '', '']).map((answer, index) => (
<div key={index} className="flex gap-2">
<input
type="text"
value={answer}
onChange={(e) => updateAcceptedAnswer(index, e.target.value)}
className="flex-1 px-3 py-2 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200 text-sm"
placeholder={`Elfogadott válasz ${index + 1}...`}
/>
{(card.acceptedAnswers?.length || 0) > 1 && (
<button
onClick={() => removeAcceptedAnswer(index)}
className="p-2 rounded-lg bg-[color:var(--color-error)]/10 text-[color:var(--color-error)] hover:bg-[color:var(--color-error)]/20 transition-all duration-200"
>
<FaTrash className="text-xs" />
</button>
)}
</div>
))}
</div>
<div className="text-xs text-[color:var(--color-text-muted)] mt-2">
Vesszővel elválasztva is megadhatsz több elfogadott választ egy mezőben
</div>
</div>
{/* Beállítások */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={card.caseSensitive || false}
onChange={(e) => updateField('caseSensitive', e.target.checked)}
className="w-4 h-4 text-[color:var(--color-success)] border-[color:var(--color-surface-selected)] rounded focus:ring-[color:var(--color-success)]"
/>
<span className="text-[color:var(--color-text)]">Kis/nagy betű érzékeny</span>
</label>
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={card.exactMatch || false}
onChange={(e) => updateField('exactMatch', e.target.checked)}
className="w-4 h-4 text-[color:var(--color-success)] border-[color:var(--color-surface-selected)] rounded focus:ring-[color:var(--color-success)]"
/>
<span className="text-[color:var(--color-text)]">Pontos egyezés</span>
</label>
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={card.partialAccepted || true}
onChange={(e) => updateField('partialAccepted', e.target.checked)}
className="w-4 h-4 text-[color:var(--color-success)] border-[color:var(--color-surface-selected)] rounded focus:ring-[color:var(--color-success)]"
/>
<span className="text-[color:var(--color-text)]">Részleges elfogadás</span>
</label>
</div>
{/* Tipp */}
<div className="mt-4">
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
Tipp (opcionális)
</label>
<input
type="text"
value={card.hint || ''}
onChange={(e) => updateField('hint', e.target.value)}
className="w-full px-4 py-2 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200"
placeholder="Segítő tipp a játékosoknak..."
/>
</div>
</div>
)}
{/* Közös beállítások */}
<div className="bg-[color:var(--color-surface)] rounded-xl p-6">
<h3 className="text-lg font-semibold text-[color:var(--color-text)] mb-4">
Beállítások
</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{/* Pontszám */}
<div>
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
💰 Pontszám
</label>
<input
type="number"
value={card.points || 10}
onChange={(e) => updateField('points', parseInt(e.target.value) || 0)}
className="w-full px-4 py-2 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200"
min="0"
max="1000"
/>
</div>
{/* Időlimit */}
<div>
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
Időlimit
</label>
<select
value={card.timeLimit || 30}
onChange={(e) => updateField('timeLimit', parseInt(e.target.value))}
className="w-full px-4 py-2 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200"
>
{timeLimits.map(time => (
<option key={time.value} value={time.value}>
{time.label}
</option>
))}
</select>
</div>
{/* Karakterlimit (csak szöveges válasznál) */}
{card.subType === 'text' && (
<div>
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
📝 Karakterlimit
</label>
<input
type="number"
value={card.characterLimit || 100}
onChange={(e) => updateField('characterLimit', parseInt(e.target.value) || 0)}
className="w-full px-4 py-2 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200"
min="10"
max="500"
/>
</div>
)}
</div>
{/* Magyarázat */}
<div className="mt-6">
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
💡 Magyarázat (opcionális)
</label>
<textarea
value={card.explanation || ''}
onChange={(e) => updateField('explanation', e.target.value)}
className="w-full px-4 py-3 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200 resize-none"
rows="3"
placeholder="Magyarázat a helyes válaszhoz..."
/>
</div>
</div>
</div>
)
}