PairWordCard: multi-select — toggle multiple words before confirming
Selected IDs tracked in a Set; correct only if all right words chosen and no wrong ones. Missed correct answers shown green after confirm; wrong picks shown red. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -84,8 +84,9 @@ function shuffle(arr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function PairWordCard({ card, onComplete }) {
|
export default function PairWordCard({ card, onComplete }) {
|
||||||
const [selected, setSelected] = useState(null) // chosen but not yet confirmed
|
const [selectedIds, setSelectedIds] = useState(new Set()) // toggled, not yet confirmed
|
||||||
const [picked, setPicked] = useState(null) // confirmed answer
|
const [confirmed, setConfirmed] = useState(false) // after Bestätigen
|
||||||
|
const [isCorrect, setIsCorrect] = useState(false)
|
||||||
const [activeChip, setActiveChip] = useState(null)
|
const [activeChip, setActiveChip] = useState(null)
|
||||||
|
|
||||||
const lang = card.lang || 'de'
|
const lang = card.lang || 'de'
|
||||||
@@ -113,17 +114,25 @@ export default function PairWordCard({ card, onComplete }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleSelect(opt) {
|
function handleSelect(opt) {
|
||||||
if (picked) return
|
if (confirmed) return
|
||||||
setSelected(prev => prev?.id === opt.id ? null : opt)
|
setSelectedIds(prev => {
|
||||||
|
const next = new Set(prev)
|
||||||
|
next.has(opt.id) ? next.delete(opt.id) : next.add(opt.id)
|
||||||
|
return next
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleConfirm() {
|
function handleConfirm() {
|
||||||
if (!selected || picked) return
|
if (selectedIds.size === 0 || confirmed) return
|
||||||
setActiveChip(null)
|
setActiveChip(null)
|
||||||
setPicked(selected)
|
const correctIds = new Set(options.filter(o => o.correct).map(o => o.id))
|
||||||
const r = selected.correct ? 'correct' : 'wrong'
|
const allCorrectSelected = [...correctIds].every(id => selectedIds.has(id))
|
||||||
if (selected.correct) triggerConfetti()
|
const noWrongSelected = [...selectedIds].every(id => correctIds.has(id))
|
||||||
setTimeout(() => onComplete(r), 900)
|
const ok = allCorrectSelected && noWrongSelected
|
||||||
|
setIsCorrect(ok)
|
||||||
|
setConfirmed(true)
|
||||||
|
if (ok) triggerConfetti()
|
||||||
|
setTimeout(() => onComplete(ok ? 'correct' : 'wrong'), 900)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -150,28 +159,27 @@ export default function PairWordCard({ card, onComplete }) {
|
|||||||
<p className="pair-question">
|
<p className="pair-question">
|
||||||
{resolveSentence(sentence, card.placeholders, handleChipClick, activeChip?.id)}
|
{resolveSentence(sentence, card.placeholders, handleChipClick, activeChip?.id)}
|
||||||
</p>
|
</p>
|
||||||
{hint && !picked && (
|
{hint && !confirmed && (
|
||||||
<p className="pair-hint">
|
<p className="pair-hint">
|
||||||
{resolveSentence(hint, card.placeholders, null, null)}
|
{resolveSentence(hint, card.placeholders, null, null)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
<div className="pair-options">
|
<div className="pair-options">
|
||||||
{options.map((opt) => {
|
{options.map((opt) => {
|
||||||
const label = opt[lang] || opt.de || '…'
|
const label = opt[lang] || opt.de || '…'
|
||||||
const hint2 = opt[native] || null
|
const hint2 = opt[native] || null
|
||||||
let cls = 'pair-option-btn'
|
let cls = 'pair-option-btn'
|
||||||
if (picked) {
|
if (confirmed) {
|
||||||
if (opt.id === picked.id) cls += opt.correct ? ' correct' : ' wrong'
|
if (selectedIds.has(opt.id)) cls += opt.correct ? ' correct' : ' wrong'
|
||||||
else if (opt.correct) cls += ' correct'
|
else if (opt.correct) cls += ' correct'
|
||||||
} else if (selected?.id === opt.id) {
|
} else if (selectedIds.has(opt.id)) {
|
||||||
cls += ' selected'
|
cls += ' selected'
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<button key={opt.id} className={cls} onClick={() => handleSelect(opt)} disabled={!!picked}>
|
<button key={opt.id} className={cls} onClick={() => handleSelect(opt)} disabled={confirmed}>
|
||||||
{label}
|
{label}
|
||||||
{picked && hint2 && opt.correct && (
|
{confirmed && hint2 && opt.correct && (
|
||||||
<span style={{ display: 'block', fontSize: '11px', opacity: 0.85, marginTop: 2 }}>
|
<span style={{ display: 'block', fontSize: '11px', opacity: 0.85, marginTop: 2 }}>
|
||||||
{hint2}
|
{hint2}
|
||||||
</span>
|
</span>
|
||||||
@@ -181,21 +189,21 @@ export default function PairWordCard({ card, onComplete }) {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{picked && (
|
{confirmed && (
|
||||||
<p className={`pair-feedback ${picked.correct ? 'correct' : 'wrong'}`}>
|
<p className={`pair-feedback ${isCorrect ? 'correct' : 'wrong'}`}>
|
||||||
{picked.correct
|
{isCorrect
|
||||||
? '✓ Richtig!'
|
? '✓ Richtig!'
|
||||||
: `✗ Richtig wäre: ${options.find(o => o.correct)?.[lang] || options.find(o => o.correct)?.de || '?'}`
|
: `✗ Richtig wären: ${options.filter(o => o.correct).map(o => o[lang] || o.de).join(', ')}`
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!picked && (
|
{!confirmed && (
|
||||||
<div className="pair-btn-row" style={{ marginTop: 12 }}>
|
<div className="pair-btn-row" style={{ marginTop: 12 }}>
|
||||||
<button
|
<button
|
||||||
className={`pair-btn ${selected ? 'pair-btn-primary' : 'pair-btn-locked'}`}
|
className={`pair-btn ${selectedIds.size > 0 ? 'pair-btn-primary' : 'pair-btn-locked'}`}
|
||||||
onClick={handleConfirm}
|
onClick={handleConfirm}
|
||||||
disabled={!selected}
|
disabled={selectedIds.size === 0}
|
||||||
>
|
>
|
||||||
Bestätigen
|
Bestätigen
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user