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"
@@ -11,15 +11,15 @@ import { createDeck } from '../../api/deckApi'
import { notifySuccess, notifyError, notifyWarning } from "../../components/Toastify/toastifyServices"
export default function DeckCreator() {
const { deckId } = useParams() // URL-ből deck ID (új deck esetén undefined)
const { deckId } = useParams()
const navigate = useNavigate()
// Deck alapadatok
const [deck, setDeck] = useState({
id: null,
name: "Új Pakli",
type: "QUESTION", // QUESTION, LUCK, JOKER - backend formátum
privacy: "private", // private, public
type: "QUESTION",
privacy: "private",
description: "",
cards: []
})
@@ -45,25 +45,38 @@ export default function DeckCreator() {
}
}, [deckId])
const loadDeck = async (id) => {
// Mock adatok később backendből jön majd
const mockDeck = {
id,
name: "Demo Deck",
type: "QUESTION",
privacy: "public",
description: "Ez egy teszt deck",
cards: []
}
setDeck(mockDeck)
}
const handleDeckUpdate = (updates) => {
setDeck(prev => ({ ...prev, ...updates }))
}
const handleSaveDeck = async () => {
try {
// Konvertálás: Frontend string -> Backend enum number
// Típus konverzió backendhez
const typeMapping = {
'LUCK': 0,
'JOKER': 1,
'QUESTION': 2
}
const payload = {
name: deck.name,
type: typeMapping[deck.type] ?? 2, // Alapértelmezett: QUESTION
name: deck.name?.trim() || "Névtelen pakli",
type: typeMapping[deck.type] ?? 2,
ctype: deck.privacy === 'public' ? 'PUBLIC' : 'PRIVATE',
description: deck.description || '',
cards: deck.cards
description: deck.description || "",
cards: deck.cards || []
}
const saved = await createDeck(payload)
@@ -75,16 +88,14 @@ export default function DeckCreator() {
}))
console.log('Deck saved (backend):', saved)
notifySuccess('Deck sikeresen mentve!')
notifySuccess('Pakli sikeresen elmentve!')
} catch (error) {
console.error('Mentési hiba:', error)
notifyError('Hiba történt a mentés során: ' + (error?.response?.data?.error || error.message || String(error)))
notifyError('Hiba történt a mentés során: ' + (error?.response?.data?.error || error.message))
}
}
// 🔧 Itt korábban volt confirm(), de most eltávolítottuk
const handleBack = () => {
// Egyszerű visszalépés ha akarsz, később adhatunk hozzá saját modalt
navigate("/decks")
}
@@ -100,33 +111,34 @@ export default function DeckCreator() {
setNewCardType(null)
}
// 💡 Demo verzió: beállítások szekció kihagyva
const handleSaveCard = (cardData) => {
try {
// Biztosítjuk az alapértelmezett consequence értékeket
if (cardData.section === "settings") {
console.log("Beállítások szekció kihagyva (demo verzió)")
return
}
const defaultConsequence = { type: 0, value: 1 }
const defaultWrongConsequence = { type: 1, value: 1 }
const updatedCard = {
...cardData,
id: isCreatingCard ? Date.now() : cardData.id,
consequence: cardData.consequence || defaultConsequence,
// wrongConsequence csak QUESTION és JOKER típusoknál
...(cardData.type === 'QUESTION' || cardData.type === 'JOKER'
...(cardData.type === 'QUESTION' || cardData.type === 'JOKER' || cardData.type === 'PAIRING'
? { wrongConsequence: cardData.wrongConsequence || defaultWrongConsequence }
: {}
)
}
let wasInvalidCardDeleted = false
setDeck(prev => {
// Ellenőrizzük, vannak-e nem megfelelő típusú kártyák
const invalidCards = prev.cards.filter(card => card.type !== prev.type)
// Ha új kártyát mentünk megfelelő típussal és vannak nem megfelelők
if (isCreatingCard && cardData.type === prev.type && invalidCards.length > 0) {
wasInvalidCardDeleted = true
return {
...prev,
cards: [
@@ -135,11 +147,10 @@ export default function DeckCreator() {
]
}
}
// Alap mentési logika
return {
...prev,
cards: isCreatingCard
cards: isCreatingCard
? [...prev.cards, updatedCard]
: prev.cards.map(card => card.id === updatedCard.id ? updatedCard : card)
}
@@ -148,8 +159,7 @@ export default function DeckCreator() {
setSelectedCard(updatedCard)
setIsCreatingCard(false)
setNewCardType(null)
// Csak egy értesítés
if (wasInvalidCardDeleted) {
const invalidCount = deck.cards.filter(card => card.type !== deck.type).length
notifyWarning(`Kártya mentve! ${invalidCount} db nem megfelelő típusú kártya törlésre került.`)
@@ -162,8 +172,11 @@ export default function DeckCreator() {
}
}
// 🔧 Itt is confirm() volt most a CardsList popupja kezeli a megerősítést
// 💬 Felugró ablak törlés előtt
const handleDeleteCard = (cardId) => {
const confirmDelete = window.confirm("Biztosan törölni szeretnéd ezt a kártyát?")
if (!confirmDelete) return
setDeck(prev => ({
...prev,
cards: prev.cards.filter(card => card.id !== cardId)
@@ -172,6 +185,8 @@ export default function DeckCreator() {
if (selectedCard?.id === cardId) {
setSelectedCard(null)
}
notifySuccess("Kártya törölve a pakliból!")
}
return (