From b73d1528c49701f1c483f18e35699403d0bd8875 Mon Sep 17 00:00:00 2001 From: zsola03 Date: Thu, 23 Oct 2025 20:18:52 +0200 Subject: [PATCH] =?UTF-8?q?10.23=20zsola=20hib=C3=A1k=20+=20Deckek=20list?= =?UTF-8?q?=C3=A1z=C3=A1sa=20megoldva?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/DeckCreator/CardEditor.jsx | 72 +++++++++- .../components/DeckCreator/DeckManager.jsx | 135 ++++++++++++++++-- .../components/DeckCreator/TaskCardEditor.jsx | 11 +- .../src/pages/DeckCreator/DeckCreator.jsx | 73 ++++++---- 4 files changed, 244 insertions(+), 47 deletions(-) diff --git a/SerpentRace_Frontend/src/components/DeckCreator/CardEditor.jsx b/SerpentRace_Frontend/src/components/DeckCreator/CardEditor.jsx index 2fc5cbe3..9ca7797e 100644 --- a/SerpentRace_Frontend/src/components/DeckCreator/CardEditor.jsx +++ b/SerpentRace_Frontend/src/components/DeckCreator/CardEditor.jsx @@ -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()) { diff --git a/SerpentRace_Frontend/src/components/DeckCreator/DeckManager.jsx b/SerpentRace_Frontend/src/components/DeckCreator/DeckManager.jsx index 6aa60f09..301839db 100644 --- a/SerpentRace_Frontend/src/components/DeckCreator/DeckManager.jsx +++ b/SerpentRace_Frontend/src/components/DeckCreator/DeckManager.jsx @@ -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 (
@@ -263,6 +285,36 @@ const DeckManager = () => { )}
+ + {/* Items per page selector and pagination info */} +
+
+ + Elemek oldalanként: + + +
+ +
+ {totalDecks > 0 ? ( + <> + {startIndex + 1}-{Math.min(endIndex, totalDecks)} / {totalDecks} pakli + + ) : ( + <>0 pakli + )} +
+
+ {/* Decks Grid */}
{/* Create New Deck (Mockup) */} @@ -280,7 +332,7 @@ const DeckManager = () => { {!loading && filteredDecks.length === 0 && (
Nincsenek mentett paklik.
)} - {!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 = () => { ) })}
+ + {/* Pagination Controls */} + {totalPages > 1 && ( +
+ + +
+ {[...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 ( + + ) + } else if ( + pageNum === currentPage - 2 || + pageNum === currentPage + 2 + ) { + return ( + + ... + + ) + } + return null + })} +
+ + +
+ )} {/* Deck Info Popup */} diff --git a/SerpentRace_Frontend/src/components/DeckCreator/TaskCardEditor.jsx b/SerpentRace_Frontend/src/components/DeckCreator/TaskCardEditor.jsx index cace7ed8..018e4531 100644 --- a/SerpentRace_Frontend/src/components/DeckCreator/TaskCardEditor.jsx +++ b/SerpentRace_Frontend/src/components/DeckCreator/TaskCardEditor.jsx @@ -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 }) {
- {(card.acceptedAnswers || ['', '', '']).map((answer, index) => ( + {(card.acceptedAnswers || ['']).map((answer, index) => (
- {(card.acceptedAnswers?.length || 0) > 1 && ( + {(card.acceptedAnswers?.length || 1) > 1 && (