This commit is contained in:
GitG0r0
2025-09-12 19:44:22 +02:00
parent 37f81f25a7
commit d1377291ab
10 changed files with 1731 additions and 1 deletions
@@ -0,0 +1,500 @@
// 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>
)
}