10.23 zsola hibák + Deckek listázása megoldva #63
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user