10.23 zsola hibák + Deckek listázása megoldva

This commit is contained in:
2025-10-23 20:18:52 +02:00
parent 387ebbc64d
commit b73d1528c4
4 changed files with 244 additions and 47 deletions
@@ -33,6 +33,19 @@ export default function CardEditor({ card, isCreating, cardType, onSave, onCance
options: ['', '', '', ''],
correctAnswer: 0,
explanation: '',
acceptedAnswers: [''],
wrongConsequence: { type: 1, value: 1 }
}
case 'PAIRING':
case 'MATCHING':
return {
...baseData,
type: 'QUESTION',
subType: 'matching',
taskDescription: '',
leftItems: ['', ''],
rightItems: ['', ''],
correctPairs: { 0: 0, 1: 1 },
wrongConsequence: { type: 1, value: 1 }
}
case 'JOKER':
@@ -84,13 +97,60 @@ export default function CardEditor({ card, isCreating, cardType, onSave, onCance
}
if (data.type === 'QUESTION') {
if (!data.question && !data.statement) {
notifyError("Kérdés vagy állítás megadása kötelező!")
return false
// Quiz típus validálás
if (data.subType === 'quiz') {
if (!data.question || !data.question.trim()) {
notifyError("Kérdés megadása kötelező!")
return false
}
if (data.options && data.options.some(opt => !opt.trim())) {
notifyError("Minden válaszlehetőséget ki kell tölteni!")
return false
}
}
if (data.subType === 'quiz' && data.options && data.options.some(opt => !opt.trim())) {
notifyError("Minden válaszlehetőséget ki kell tölteni!")
return false
// Igaz/Hamis típus validálás
else if (data.subType === 'truefalse') {
if (!data.statement || !data.statement.trim()) {
notifyError("Állítás megadása kötelező!")
return false
}
if (data.isTrue === undefined || data.isTrue === null) {
notifyError("Válaszd ki, hogy az állítás igaz vagy hamis!")
return false
}
}
// Párosítás típus validálás
else if (data.subType === 'matching') {
if (!data.taskDescription || !data.taskDescription.trim()) {
notifyError("Feladat leírása kötelező!")
return false
}
if (!data.leftItems || data.leftItems.length === 0) {
notifyError("Legalább egy párosítást meg kell adni!")
return false
}
if (data.leftItems.some(item => !item.trim()) || data.rightItems.some(item => !item.trim())) {
notifyError("Minden párosítási elemet ki kell tölteni!")
return false
}
}
// Szöveges válasz típus validálás
else if (data.subType === 'text') {
if (!data.question || !data.question.trim()) {
notifyError("Kérdés megadása kötelező!")
return false
}
if (!data.acceptedAnswers || data.acceptedAnswers.length === 0 || data.acceptedAnswers.every(ans => !ans.trim())) {
notifyError("Legalább egy elfogadott választ meg kell adni!")
return false
}
}
// Általános validálás (ha nincs subType megadva)
else {
if (!data.question && !data.statement) {
notifyError("Kérdés vagy állítás megadása kötelező!")
return false
}
}
} else if (data.type === 'JOKER') {
if (!data.text || !data.text.trim()) {
@@ -9,6 +9,8 @@ import {
FaSortAlphaDown,
FaSortAlphaUp,
FaQuestionCircle,
FaChevronLeft,
FaChevronRight,
} from "react-icons/fa"
import SearchBox from "../Search/SearchBox"
import PopUp from "../PopUp/PopUp"
@@ -70,18 +72,25 @@ const DeckManager = () => {
const [search, setSearch] = useState("")
const [showSortHelp, setShowSortHelp] = useState(false)
const [selectedDeck, setSelectedDeck] = useState(null)
const [decks, setDecks] = useState([])
const [allDecks, setAllDecks] = useState([]) // Összes pakli
const [loading, setLoading] = useState(false)
const [itemsPerPage, setItemsPerPage] = useState(20)
const [currentPage, setCurrentPage] = useState(1)
// Load all decks once
useEffect(() => {
let mounted = true
const load = async () => {
setLoading(true)
try {
const result = await import('../../api/deckApi').then(m => m.getDecksPage(0, 49))
// Load all decks (0-99 is the max limit = 100 decks)
const result = await import('../../api/deckApi').then(m => m.getDecksPage(0, 99))
if (!mounted) return
// map backend deck shape to UI shape
const mapped = result.decks.map(d => ({
console.log('Loaded decks:', result) // Debug
// Map backend deck shape to UI shape
const mapped = (result.decks || []).map(d => ({
id: d.id,
name: d.name,
type: d.type === 2 ? 'Question' : d.type === 1 ? 'Joker' : 'Luck',
@@ -89,7 +98,9 @@ const DeckManager = () => {
origin: d.ctype === 2 ? 'Vállalati' : d.ctype === 0 ? 'Mind' : 'Saját',
raw: d
}))
setDecks(mapped)
console.log('Mapped decks:', mapped) // Debug
setAllDecks(mapped)
} catch (err) {
console.error('Failed to load decks', err)
} finally {
@@ -101,8 +112,7 @@ const DeckManager = () => {
}, [])
// Filter logic
const sourceDecks = decks
let filteredDecks = sourceDecks.filter((deck) => {
let filteredDecks = allDecks.filter((deck) => {
const typeMatch = selectedType === "All" || deck.type === selectedType
const originMatch = selectedOrigin === "Mind" || deck.origin === selectedOrigin
const searchMatch = !search || deck.name.toLowerCase().includes(search.toLowerCase())
@@ -123,6 +133,18 @@ const DeckManager = () => {
return 0
})
// Pagination logic - frontend only
const totalDecks = filteredDecks.length
const totalPages = Math.ceil(totalDecks / itemsPerPage)
const startIndex = (currentPage - 1) * itemsPerPage
const endIndex = startIndex + itemsPerPage
const paginatedDecks = filteredDecks.slice(startIndex, endIndex)
// Reset to page 1 when filters or items per page change
useEffect(() => {
setCurrentPage(1)
}, [selectedType, selectedOrigin, search, sortBy, itemsPerPage])
return (
<div className="w-full flex flex-col bg-[color:var(--color-background)]">
<div className="w-full max-w-[1200px] mx-auto px-4 py-10">
@@ -263,6 +285,36 @@ const DeckManager = () => {
)}
</div>
</div>
{/* Items per page selector and pagination info */}
<div className="flex flex-col md:flex-row gap-4 justify-between items-center mb-6 bg-[color:var(--color-surface)]/60 backdrop-blur-lg rounded-xl px-6 py-3 shadow">
<div className="flex items-center gap-3">
<span className="text-[color:var(--color-text-muted)] text-sm font-medium">
Elemek oldalanként:
</span>
<select
value={itemsPerPage}
onChange={(e) => setItemsPerPage(Number(e.target.value))}
className="px-3 py-1.5 rounded-lg bg-[color:var(--color-background)] text-[color:var(--color-text)] border border-[color:var(--color-surface-selected)] focus:ring-2 focus:ring-[color:var(--color-success)] outline-none transition-all duration-200"
>
<option value={20}>20</option>
<option value={30}>30</option>
<option value={40}>40</option>
<option value={50}>50</option>
</select>
</div>
<div className="text-[color:var(--color-text-muted)] text-sm">
{totalDecks > 0 ? (
<>
{startIndex + 1}-{Math.min(endIndex, totalDecks)} / {totalDecks} pakli
</>
) : (
<>0 pakli</>
)}
</div>
</div>
{/* Decks Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-8 mt-8">
{/* Create New Deck (Mockup) */}
@@ -280,7 +332,7 @@ const DeckManager = () => {
{!loading && filteredDecks.length === 0 && (
<div className="col-span-full text-center text-[color:var(--color-text-muted)]">Nincsenek mentett paklik.</div>
)}
{!loading && filteredDecks.map((deck) => {
{!loading && paginatedDecks.map((deck) => {
const deckType = deckTypes.find((t) => t.label === deck.type)
const borderColor = deckType ? deckType.color : "var(--color-success)"
return (
@@ -317,6 +369,73 @@ const DeckManager = () => {
)
})}
</div>
{/* Pagination Controls */}
{totalPages > 1 && (
<div className="flex justify-center items-center gap-2 mt-8">
<button
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
disabled={currentPage === 1}
className={`px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2 ${
currentPage === 1
? 'bg-[color:var(--color-surface)] text-[color:var(--color-text-muted)] cursor-not-allowed'
: 'bg-[color:var(--color-success)] text-[color:var(--color-text-inverse)] hover:bg-[color:var(--color-success)]/80 hover:scale-105'
}`}
>
<FaChevronLeft />
Előző
</button>
<div className="flex items-center gap-2">
{[...Array(totalPages)].map((_, index) => {
const pageNum = index + 1
// Show first page, last page, current page and neighbors
if (
pageNum === 1 ||
pageNum === totalPages ||
(pageNum >= currentPage - 1 && pageNum <= currentPage + 1)
) {
return (
<button
key={pageNum}
onClick={() => setCurrentPage(pageNum)}
className={`w-10 h-10 rounded-lg font-medium transition-all duration-200 ${
currentPage === pageNum
? 'bg-[color:var(--color-success)] text-[color:var(--color-text-inverse)] scale-110 shadow-lg'
: 'bg-[color:var(--color-surface)] text-[color:var(--color-text)] hover:bg-[color:var(--color-surface-selected)]'
}`}
>
{pageNum}
</button>
)
} else if (
pageNum === currentPage - 2 ||
pageNum === currentPage + 2
) {
return (
<span key={pageNum} className="text-[color:var(--color-text-muted)]">
...
</span>
)
}
return null
})}
</div>
<button
onClick={() => setCurrentPage(prev => Math.min(totalPages, prev + 1))}
disabled={currentPage === totalPages}
className={`px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center gap-2 ${
currentPage === totalPages
? 'bg-[color:var(--color-surface)] text-[color:var(--color-text-muted)] cursor-not-allowed'
: 'bg-[color:var(--color-success)] text-[color:var(--color-text-inverse)] hover:bg-[color:var(--color-success)]/80 hover:scale-105'
}`}
>
Következő
<FaChevronRight />
</button>
</div>
)}
</div>
{/* Deck Info Popup */}
@@ -79,13 +79,16 @@ export default function TaskCardEditor({ card, onChange }) {
}
const updateAcceptedAnswer = (index, value) => {
const newAnswers = [...card.acceptedAnswers]
const currentAnswers = card.acceptedAnswers || ['']
const newAnswers = [...currentAnswers]
newAnswers[index] = value
onChange({ acceptedAnswers: newAnswers })
}
const removeAcceptedAnswer = (index) => {
const newAnswers = card.acceptedAnswers.filter((_, i) => i !== index)
const currentAnswers = card.acceptedAnswers || ['']
if (currentAnswers.length <= 1) return // Legalább egy válasz maradjon
const newAnswers = currentAnswers.filter((_, i) => i !== index)
onChange({ acceptedAnswers: newAnswers })
}
@@ -375,7 +378,7 @@ export default function TaskCardEditor({ card, onChange }) {
</div>
<div className="space-y-2">
{(card.acceptedAnswers || ['', '', '']).map((answer, index) => (
{(card.acceptedAnswers || ['']).map((answer, index) => (
<div key={index} className="flex gap-2">
<input
type="text"
@@ -385,7 +388,7 @@ export default function TaskCardEditor({ card, onChange }) {
placeholder={`Elfogadott válasz ${index + 1}...`}
/>
{(card.acceptedAnswers?.length || 0) > 1 && (
{(card.acceptedAnswers?.length || 1) > 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"