import React, { useState, useEffect } from "react"
import { useParams } from "react-router-dom"
import HandleNavigate from "../../utils/HandleNavigate/HandleNavigate"
import {
FaArrowLeft,
FaFilter,
FaArrowUp,
FaArrowDown,
FaSortAlphaDown,
FaSortAlphaUp,
FaQuestionCircle,
FaChevronLeft,
FaChevronRight,
} from "react-icons/fa"
import Navbar from "../../components/Navbar/Navbar"
import SearchBox from "../../components/Search/SearchBox"
import PopUp from "../../components/PopUp/PopUp"
import { getDeckById } from "../../api/deckApi"
const Card_display = () => {
const { deckId } = useParams()
const { goDecks } = HandleNavigate()
const [deck, setDeck] = useState(null)
const [cards, setCards] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [search, setSearch] = useState("")
const [sortBy, setSortBy] = useState("index")
const [showSortHelp, setShowSortHelp] = useState(false)
const [itemsPerPage, setItemsPerPage] = useState(20)
const [currentPage, setCurrentPage] = useState(1)
const [flippedCards, setFlippedCards] = useState(new Set()) // Track which cards are flipped
// Load deck and parse cards
useEffect(() => {
let mounted = true
const load = async () => {
setLoading(true)
setError(null)
try {
const result = await getDeckById(deckId)
if (!mounted) return
setDeck(result)
// Parse cards from JSON if it's a string
let parsedCards = []
if (result.cards) {
if (typeof result.cards === 'string') {
try {
parsedCards = JSON.parse(result.cards)
} catch (e) {
console.error('Failed to parse cards JSON:', e)
}
} else if (Array.isArray(result.cards)) {
parsedCards = result.cards
}
}
setCards(parsedCards)
} catch (err) {
console.error('Failed to load deck', err)
if (!mounted) return
setError(err.message || 'Hiba történt a pakli betöltése közben.')
} finally {
if (mounted) setLoading(false)
}
}
load()
return () => { mounted = false }
}, [deckId])
// Filter logic
let filteredCards = cards.filter((card) => {
if (!search) return true
const searchLower = search.toLowerCase()
// Check text field (or fallback to question/statement for old data)
const questionText = card.text || card.question || card.statement || ''
const questionMatch = questionText.toLowerCase().includes(searchLower)
const answersMatch = Array.isArray(card.options)
? card.options.some(opt => opt && opt.toLowerCase().includes(searchLower))
: Array.isArray(card.answers)
? card.answers.some(a => a && a.toLowerCase().includes(searchLower))
: false
return questionMatch || answersMatch
})
// Sort logic
filteredCards = [...filteredCards].sort((a, b) => {
if (sortBy === "index") {
// Keep original order
return 0
} else if (sortBy === "question-asc") {
const aText = a.text || a.question || a.statement || ''
const bText = b.text || b.question || b.statement || ''
return aText.localeCompare(bText)
} else if (sortBy === "question-desc") {
const aText = a.text || a.question || a.statement || ''
const bText = b.text || b.question || b.statement || ''
return bText.localeCompare(aText)
} else if (sortBy === "answers-asc") {
const aCount = Array.isArray(a.options) ? a.options.length : Array.isArray(a.answers) ? a.answers.length : 0
const bCount = Array.isArray(b.options) ? b.options.length : Array.isArray(b.answers) ? b.answers.length : 0
return aCount - bCount
} else if (sortBy === "answers-desc") {
const aCount = Array.isArray(a.options) ? a.options.length : Array.isArray(a.answers) ? a.answers.length : 0
const bCount = Array.isArray(b.options) ? b.options.length : Array.isArray(b.answers) ? b.answers.length : 0
return bCount - aCount
}
return 0
})
// Pagination logic
const totalCards = filteredCards.length
const totalPages = Math.ceil(totalCards / itemsPerPage)
const startIndex = (currentPage - 1) * itemsPerPage
const endIndex = startIndex + itemsPerPage
const paginatedCards = filteredCards.slice(startIndex, endIndex)
// Reset to page 1 when filters or items per page change
useEffect(() => {
setCurrentPage(1)
}, [search, sortBy, itemsPerPage])
const deckTypes = {
0: { label: "Szerencse", color: "var(--color-luck)" },
1: { label: "Joker", color: "var(--color-fun)" },
2: { label: "Kérdés", color: "var(--color-question)" },
}
// Card subtype Hungarian labels - UPDATED based on actual data
const cardSubTypeLabels = {
// String types (from DeckCreator)
"quiz": "Quiz ",
"truefalse": "Igaz/Hamis",
"text": "Szöveges válasz",
"matching": "Párosítás",
"QUESTION": "Kérdés",
"LUCK": "Szerencse",
"JOKER": "Joker",
"joker": "Joker",
"luck": "Szerencse",
// Backend CardType enum (numeric):
"0": "Quiz", // CardType.QUIZ = 0
"1": "Párosítás", // CardType.SENTENCE_PAIRING = 1
"2": "Szöveges válasz", // CardType.OWN_ANSWER = 2
"3": "Igaz/Hamis", // CardType.TRUE_FALSE = 3
"4": "Közelítés", // CardType.CLOSER = 4
0: "Quiz",
1: "Párosítás",
2: "Szöveges válasz",
3: "Igaz/Hamis",
4: "Közelítés"
}
const currentDeckType = deck ? (deckTypes[deck.type] || { label: "Ismeretlen", color: "var(--color-success)" }) : null
const toggleCardFlip = (cardId) => {
setFlippedCards(prev => {
const newSet = new Set(prev)
if (newSet.has(cardId)) {
newSet.delete(cardId)
} else {
newSet.add(cardId)
}
return newSet
})
}
return (
{/* Header with back button */}
{deck && (
{deck.name}
{currentDeckType && (
{currentDeckType.label}
)}
)}
{/* Loading / Error states */}
{loading && (
Betöltés...
)}
{error && (
{error}
)}
{/* Filters and controls */}
{!loading && !error && (
<>
setSearch(e.target.value)}
width={300}
placeholder="Keresés..."
className="w-full sm:w-auto"
/>
Rendezés:
{showSortHelp && (
setShowSortHelp(false)}>
Rendezési lehetőségek magyarázata
-
Eredeti sorrend – A kártyák eredeti sorrendben jelennek meg
-
Kérdés A→Z – Kérdések ABC sorrendben (A-tól Z-ig)
-
Kérdés Z→A – Kérdések fordított ABC sorrendben (Z-től A-ig)
-
Válaszok száma ↑ – Kevesebb választól a több válasz felé
-
Válaszok száma ↓ – Több választól a kevesebb válasz felé
)}
{/* Items per page selector and pagination info */}
Oldalanként:
{totalCards > 0 ? (
<>
{startIndex + 1}-{Math.min(endIndex, totalCards)} / {totalCards} kártya
{startIndex + 1}-{Math.min(endIndex, totalCards)} / {totalCards}
>
) : (
<>0 kártya>
)}
{/* Cards Grid */}
{totalCards === 0 && (
Nincsenek kártyák ebben a pakliban.
)}
{paginatedCards.map((card, idx) => {
const cardIndex = startIndex + idx + 1
const questionText = card.text || card.question || card.statement || 'Kérdés hiányzik'
// Get answers based on card type
let answerOptions = []
let correctAnswerIndex = card.correctAnswer
// Detect card type - prioritize numeric card.type over subType
let detectedType = 'undefined'
// First check deck type - if deck is JOKER or LUCK type, cards inherit that
if (deck.type === 1) {
// Deck type 1 = Joker deck
detectedType = 'joker'
} else if (deck.type === 0) {
// Deck type 0 = Luck deck
detectedType = 'luck'
} else if (card.type !== undefined && card.type !== null) {
// Check by card.type field (numeric CardType enum)
const cardType = typeof card.type === 'string' ? card.type.toLowerCase() : card.type
if (cardType === 'joker' || card.type === 'JOKER') {
detectedType = 'joker'
} else if (cardType === 'luck' || card.type === 'LUCK') {
detectedType = 'luck'
} else if (card.type === 0) {
// type 0 = Quiz (multiple choice)
detectedType = 'quiz'
} else if (card.type === 1) {
// type 1 = Matching/Pairing
detectedType = 'matching'
} else if (card.type === 2) {
// type 2 = Text answer
detectedType = 'text'
} else if (card.type === 3) {
// type 3 = True/False
detectedType = 'truefalse'
}
} else if (card.subType) {
// Fallback to subType if card.type is missing
detectedType = String(card.subType).toLowerCase()
} else if (card.leftItems && card.rightItems && card.correctPairs) {
// Has leftItems, rightItems AND correctPairs = matching
detectedType = 'matching'
} else if (card.acceptedAnswers && card.acceptedAnswers.length > 0 && card.acceptedAnswers[0] && card.acceptedAnswers[0].trim()) {
// Only detect as text if acceptedAnswers has non-empty values
detectedType = 'text'
} else if (card.isTrue !== undefined) {
detectedType = 'truefalse'
} else if (card.options && Array.isArray(card.options) && card.options.some(opt => opt && opt.trim())) {
// Has non-empty options - must be multiple choice
detectedType = 'multiplechoice'
}
// Extract consequence info for JOKER and LUCK cards
let consequenceText = null
if ((detectedType === 'joker' || detectedType === 'luck') && card.consequence) {
const consequenceLabels = {
0: 'Lépj előre',
1: 'Lépj hátra',
2: 'Kör kihagyás',
3: 'Extra kör',
5: 'Vissza a starthoz'
}
const consequenceType = consequenceLabels[card.consequence.type] || 'Ismeretlen hatás'
const consequenceValue = card.consequence.value
if (consequenceValue && [0, 1].includes(card.consequence.type)) {
consequenceText = `${consequenceType} ${consequenceValue} mezőt`
} else if (consequenceValue && [2, 3].includes(card.consequence.type)) {
consequenceText = `${consequenceType} (${consequenceValue} kör)`
} else {
consequenceText = consequenceType
}
}
if (detectedType === 'truefalse' || detectedType === '3') {
// True/False cards
answerOptions = ['Igaz', 'Hamis']
// Parse answer from various sources
let isCorrectTrue = false
if (card.isTrue !== undefined) {
isCorrectTrue = card.isTrue
} else if (card.answer !== undefined) {
const answerStr = String(card.answer).toLowerCase()
isCorrectTrue = answerStr === 'true' || answerStr === '1' || answerStr === 'igaz'
} else if (card.correctAnswer !== undefined) {
isCorrectTrue = card.correctAnswer === 0 || card.correctAnswer === true || card.correctAnswer === 'true'
}
correctAnswerIndex = isCorrectTrue ? 0 : 1
} else if (detectedType === 'quiz' || detectedType === 'multiplechoice' || detectedType === '0') {
// Quiz/Multiple choice - parse from backend answer array or frontend options
if (card.answer && Array.isArray(card.answer) && card.answer.length > 0 && typeof card.answer[0] === 'object') {
// Backend format: [{answer: "A", text: "...", correct: boolean}]
answerOptions = card.answer.map(opt => opt.text || opt.label || '')
const correctOption = card.answer.find(opt => opt.correct)
correctAnswerIndex = card.answer.indexOf(correctOption)
} else if (card.options && Array.isArray(card.options)) {
// Frontend format: ["option1", "option2"]
answerOptions = card.options.filter(opt => opt && opt.trim())
correctAnswerIndex = card.correctAnswer
}
} else if (detectedType === 'matching' || detectedType === '1') {
// Matching cards - parse from backend answer array or frontend fields
if (card.answer && Array.isArray(card.answer) && card.answer.length > 0 && typeof card.answer[0] === 'object') {
// Backend format: [{left: "...", right: "..."}]
answerOptions = card.answer.map(pair => `${pair.left} → ${pair.right}`)
correctAnswerIndex = -1 // All pairs are correct
} else if (card.leftItems && card.rightItems && card.correctPairs) {
// Frontend format with leftItems, rightItems, correctPairs
const pairs = []
for (const [leftIdx, rightIdx] of Object.entries(card.correctPairs)) {
const left = card.leftItems[parseInt(leftIdx)]
const right = card.rightItems[parseInt(rightIdx)]
if (left && right) {
pairs.push(`${left} → ${right}`)
}
}
answerOptions = pairs
correctAnswerIndex = -1 // All pairs are correct
}
} else if (detectedType === 'text' || detectedType === '2') {
// OWN_ANSWER - parse from backend answer array or frontend acceptedAnswers
if (card.answer) {
// Backend format: ["answer1", "answer2"] or single value
answerOptions = Array.isArray(card.answer) ? card.answer : [card.answer]
correctAnswerIndex = -1 // All answers are correct
} else if (card.acceptedAnswers && Array.isArray(card.acceptedAnswers)) {
// Frontend format
answerOptions = card.acceptedAnswers
correctAnswerIndex = -1 // All accepted answers are correct
}
} else if (card.options && Array.isArray(card.options)) {
// Other types with options
answerOptions = card.options.filter(opt => opt && opt.trim())
} else if (card.answers && Array.isArray(card.answers)) {
// Other card types with answers array
answerOptions = card.answers.filter(opt => opt && opt.trim())
} else if (card.acceptedAnswers && Array.isArray(card.acceptedAnswers)) {
// Fallback for accepted answers
answerOptions = card.acceptedAnswers
correctAnswerIndex = -1
}
const answerCount = answerOptions.length
const cardId = card.id || idx
const isFlipped = flippedCards.has(cardId)
return (
{detectedType === 'joker' ? (
// Joker card - no flip, just show the task
Kártya #{cardIndex}
🃏 JOKER
) : detectedType === 'luck' ? (
// Luck card - no flip, show text and consequence
Kártya #{cardIndex}
🎲 SZERENCSE
🎲
{questionText}
{consequenceText && (
)}
) : (
toggleCardFlip(cardId) : undefined}
>
{/* Front side - Question */}
Kártya #{cardIndex}
{detectedType !== 'joker' && detectedType !== 'luck' && (
{answerCount} válasz
)}
{detectedType === 'joker' && (
🃏 JOKER
)}
{detectedType === 'luck' && (
🎲 SZERENCSE
)}
{questionText}
{/* Type info only */}
Típus:
{cardSubTypeLabels[detectedType] || cardSubTypeLabels[card.subType] || cardSubTypeLabels[card.type] || detectedType || 'Ismeretlen'}
Kattints a megoldáshoz →
{/* Back side - Answer */}
{detectedType === 'joker' || detectedType === 'luck' ? 'Kártya hatás' : 'Megoldás'}
{detectedType === 'joker' || detectedType === 'luck' ? (detectedType === 'joker' ? '🃏 JOKER' : '🎲 SZERENCSE') : `${answerCount} válasz`}
{detectedType === 'joker' ? (
// Joker card - just show the task/challenge
🃏
{questionText}
A játékmester dönti el a teljesítést
) : detectedType === 'luck' ? (
// Luck card - show consequence
🎲
{consequenceText && (
)}
Azonnal végrehajt
) : answerCount > 0 ? (
Helyes válasz:
{detectedType === 'truefalse' || detectedType === '3' ? (
// True/False - show only the correct answer
(() => {
// Determine if correct answer is true
let isCorrectTrue = false
if (card.isTrue !== undefined) {
isCorrectTrue = card.isTrue
} else if (card.answer !== undefined) {
const answerStr = String(card.answer).toLowerCase()
isCorrectTrue = answerStr === 'true' || answerStr === '1' || answerStr === 'igaz'
} else if (card.correctAnswer !== undefined) {
isCorrectTrue = card.correctAnswer === 0 || card.correctAnswer === true || card.correctAnswer === 'true'
}
return (
✓ {isCorrectTrue ? 'Igaz' : 'Hamis'}
)
})()
) : detectedType === 'matching' || detectedType === '1' ? (
// Matching - show all correct pairs
{answerOptions.map((pair, idx) => (
-
✓ {pair}
))}
) : (detectedType === 'text' || detectedType === '2') && answerOptions.length > 0 ? (
// Text answers - show all accepted answers
{answerOptions.map((answer, ansIdx) => (
-
✓ {answer}
))}
) : (detectedType === 'quiz' || detectedType === 'multiplechoice' || detectedType === '0') && answerOptions.length > 0 ? (
// Quiz/Multiple choice - show all options with correct one highlighted
{answerOptions.map((option, ansIdx) => (
-
{ansIdx === correctAnswerIndex ? '✓ ' : ''}{String.fromCharCode(65 + ansIdx)}. {option}
))}
) : (
// Other types - show only the correct answer
correctAnswerIndex !== undefined && correctAnswerIndex !== -1 && answerOptions[correctAnswerIndex] ? (
✓ {answerOptions[correctAnswerIndex]}
) : (
Nincs megadva helyes válasz
)
)}
) : (
Nincs elérhető válasz
)}
Kattints a kérdéshez ←
)}
)
})}
{/* Pagination Controls */}
{totalPages > 1 && (
{[...Array(totalPages)].map((_, index) => {
const pageNum = index + 1
if (
pageNum === 1 ||
pageNum === totalPages ||
(pageNum >= currentPage - 1 && pageNum <= currentPage + 1)
) {
return (
)
} else if (
pageNum === currentPage - 2 ||
pageNum === currentPage + 2
) {
return (
...
)
}
return null
})}
)}
>
)}
)
}
export default Card_display