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:
2026-05-28 13:36:42 +02:00
parent 75bb75853e
commit 1b44e004bd

View File

@@ -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>