Files
SerpentRace/SerpentRace_Frontend/src/pages/DeckCreator/DeckCreator.jsx
T

453 lines
15 KiB
React
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// src/pages/DeckCreator/DeckCreator.jsx
// Deck Creator Page - Deck létrehozás és szerkesztés
import React, { useState, useEffect } from "react"
import { useParams, useNavigate } from "react-router-dom"
import Navbar from "../../components/Navbar/Navbar.jsx"
import DeckHeader from "../../components/DeckCreator/DeckHeader.jsx"
import CardsList from "../../components/DeckCreator/CardsList.jsx"
import CardEditor from "../../components/DeckCreator/CardEditor.jsx"
import { createDeck, getDeckById, updateDeck, deleteDeck } from '../../api/deckApi'
import { notifySuccess, notifyError, notifyWarning } from "../../components/Toastify/toastifyServices"
export default function DeckCreator() {
const { deckId } = useParams()
const navigate = useNavigate()
// Deck alapadatok
const [deck, setDeck] = useState({
id: null,
name: "Új Pakli",
type: "QUESTION",
privacy: "private",
description: "",
cards: []
})
// UI állapotok
const [selectedCard, setSelectedCard] = useState(null)
const [isCreatingCard, setIsCreatingCard] = useState(false)
const [newCardType, setNewCardType] = useState(null)
const [isLoading, setIsLoading] = useState(false)
const [showDeleteModal, setShowDeleteModal] = useState(false)
// Betöltés API-ból
useEffect(() => {
if (deckId) {
loadDeck(deckId)
} else {
setDeck({
id: null,
name: "Új Pakli",
type: "QUESTION",
privacy: "private",
description: "",
cards: []
})
}
}, [deckId])
const loadDeck = async (id) => {
setIsLoading(true)
try {
const deckData = await getDeckById(id)
console.log('Loaded deck:', deckData)
// Type mapping from backend to frontend
const typeMapping = {
0: 'LUCK',
1: 'JOKER',
2: 'QUESTION'
}
// CType mapping from backend to frontend
const ctypeMapping = {
0: 'public',
1: 'private',
2: 'organization'
}
// Process cards: convert type field from number to string
const processedCards = (deckData.cards || []).map(card => {
// A kártya type mezője a deck type-ját tükrözi (backend így küldi)
// Ezért a deck type alapján állítjuk be
return {
...card,
type: typeMapping[deckData.type] || 'QUESTION'
}
})
setDeck({
id: deckData.id,
name: deckData.name || "Névtelen pakli",
type: typeMapping[deckData.type] || 'QUESTION',
privacy: ctypeMapping[deckData.ctype] || 'private',
description: deckData.description || "",
cards: processedCards,
creationdate: deckData.creationdate,
updatedate: deckData.updatedate
})
// Success notification removed - silent load for better UX
} catch (error) {
console.error('Pakli betöltési hiba:', error)
notifyError('Hiba történt a pakli betöltése során: ' + (error?.response?.data?.error || error.message))
navigate('/decks')
} finally {
setIsLoading(false)
}
}
const handleDeckUpdate = (updates) => {
setDeck(prev => ({ ...prev, ...updates }))
}
const handleSaveDeck = async () => {
try {
// Szűrjük ki a nem megfelelő típusú kártyákat
const validCards = deck.cards.filter(card => card.type === deck.type)
const invalidCardsCount = deck.cards.length - validCards.length
// Ha voltak érvénytelen kártyák, frissítsük a state-et
if (invalidCardsCount > 0) {
setDeck(prev => ({
...prev,
cards: validCards
}))
// Értesítés a törölt kártyákról
notifyWarning(`${invalidCardsCount} db nem megfelelő típusú kártya törölve a mentés előtt.`)
}
// Tisztítsuk meg a kártyákat - konvertáljuk a backend által várt formátumra
const cleanedCards = validCards.map(card => {
// Card subType mapping to backend CardType enum
const cardTypeMapping = {
'quiz': 0, // QUIZ
'pairing': 1, // SENTENCE_PAIRING
'text': 2, // OWN_ANSWER
'truefalse': 3, // TRUE_FALSE
'closer': 4 // CLOSER
}
// Kezdjük az ID-val (ha van)
const cleanedCard = {}
if (card.id) {
cleanedCard.id = card.id
}
// Ha van subType (QUESTION típusú kártyáknál), akkor add hozzá a type mezőt
if (card.subType && cardTypeMapping[card.subType] !== undefined) {
cleanedCard.type = cardTypeMapping[card.subType]
}
// Text mező - kötelező, különböző forrásokból jöhet
cleanedCard.text = card.text || card.question || card.statement || ""
// Egyéb frontend mezők, amiket a backend is elfogad
if (card.question !== undefined) cleanedCard.question = card.question
if (card.statement !== undefined) cleanedCard.statement = card.statement
if (card.options !== undefined) cleanedCard.options = card.options
if (card.correctAnswer !== undefined) cleanedCard.correctAnswer = card.correctAnswer
if (card.leftItems !== undefined) cleanedCard.leftItems = card.leftItems
if (card.rightItems !== undefined) cleanedCard.rightItems = card.rightItems
if (card.correctPairs !== undefined) cleanedCard.correctPairs = card.correctPairs
if (card.acceptedAnswers !== undefined) cleanedCard.acceptedAnswers = card.acceptedAnswers
if (card.hint !== undefined) cleanedCard.hint = card.hint
// Answer mező (ha van)
if (card.answer !== undefined && card.answer !== null) {
cleanedCard.answer = card.answer
}
// Csak LUCK típusú kártyáknál add hozzá a consequence-t
if (deck.type === 'LUCK' && card.consequence) {
cleanedCard.consequence = card.consequence
}
return cleanedCard
})
// Típus konverzió backendhez
const typeMapping = {
'LUCK': 0,
'JOKER': 1,
'QUESTION': 2
}
const ctypeMapping = {
'public': 0,
'private': 1,
'organization': 2
}
const payload = {
name: deck.name?.trim() || "Névtelen pakli",
type: typeMapping[deck.type] ?? 2,
ctype: ctypeMapping[deck.privacy] ?? 1,
cards: cleanedCards
}
// Note: description field is not sent to backend as it's not supported yet
console.log('=== DECK SAVE DEBUG ===')
console.log('Deck ID:', deck.id)
console.log('Deck object:', deck)
console.log('Payload to send:', payload)
console.log('Is Update?', !!deck.id)
let saved
if (deck.id) {
// Update existing deck
console.log('Calling updateDeck with ID:', deck.id)
saved = await updateDeck(deck.id, payload)
console.log('Update response:', saved)
notifySuccess('Pakli sikeresen frissítve!')
} else {
// Create new deck
console.log('Calling createDeck')
saved = await createDeck(payload)
console.log('Create response:', saved)
notifySuccess('Pakli sikeresen létrehozva!')
}
setDeck(prev => ({
...prev,
id: saved.id ?? prev.id,
creationdate: saved.creationdate ?? prev.creationdate,
updatedate: saved.updatedate ?? prev.updatedate
}))
console.log('Deck saved (backend):', saved)
} catch (error) {
console.error('=== DECK SAVE ERROR ===')
console.error('Full error:', error)
console.error('Error response:', error?.response)
console.error('Error response data:', error?.response?.data)
console.error('Error message:', error?.message)
const errorMessage = error?.response?.data?.error
|| error?.response?.data?.message
|| error?.message
|| 'Ismeretlen hiba történt'
notifyError('Hiba történt a mentés során: ' + errorMessage)
}
}
const handleBack = () => {
navigate("/decks")
}
const handleDeleteDeck = () => {
if (!deck.id) {
notifyWarning('Nincs mit törölni - a pakli még nincs elmentve!')
return
}
setShowDeleteModal(true)
}
const handleConfirmDelete = async () => {
try {
await deleteDeck(deck.id)
setShowDeleteModal(false)
notifySuccess('Pakli sikeresen törölve!')
navigate('/decks')
} catch (error) {
console.error('Pakli törlési hiba:', error)
const errorMessage = error?.response?.data?.error
|| error?.response?.data?.message
|| error?.message
|| 'Ismeretlen hiba történt'
notifyError('Hiba történt a törlés során: ' + errorMessage)
setShowDeleteModal(false)
}
}
const handleCancelDelete = () => {
setShowDeleteModal(false)
}
const handleCreateCard = (cardType) => {
setNewCardType(cardType)
setIsCreatingCard(true)
setSelectedCard(null)
}
const handleSelectCard = (card) => {
setSelectedCard(card)
setIsCreatingCard(false)
setNewCardType(null)
}
// 💡 Demo verzió: beállítások szekció kihagyva
const handleSaveCard = (cardData) => {
try {
if (cardData.section === "settings") {
console.log("Beállítások szekció kihagyva (demo verzió)")
return
}
// Alapértelmezett consequence csak LUCK típusú kártyákhoz
const updatedCard = {
...cardData,
id: isCreatingCard ? Date.now() : cardData.id
}
// Csak LUCK típusú kártyákhoz add hozzá a consequence-t
if (cardData.type === 'LUCK') {
updatedCard.consequence = cardData.consequence || { type: 0, value: 1 }
}
let wasInvalidCardDeleted = false
setDeck(prev => {
const invalidCards = prev.cards.filter(card => card.type !== prev.type)
if (isCreatingCard && cardData.type === prev.type && invalidCards.length > 0) {
wasInvalidCardDeleted = true
return {
...prev,
cards: [
...prev.cards.filter(card => card.type === prev.type),
updatedCard
]
}
}
return {
...prev,
cards: isCreatingCard
? [...prev.cards, updatedCard]
: prev.cards.map(card => card.id === updatedCard.id ? updatedCard : card)
}
})
setSelectedCard(updatedCard)
setIsCreatingCard(false)
setNewCardType(null)
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.`)
} else {
notifySuccess('Kártya sikeresen mentve!')
}
} catch (error) {
console.error('Kártya mentési hiba:', error)
notifyError('Hiba történt a kártya mentése során')
}
}
// 💬 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)
}))
if (selectedCard?.id === cardId) {
setSelectedCard(null)
}
notifySuccess("Kártya törölve a pakliból!")
}
return (
<div className="w-full min-h-screen bg-[color:var(--color-background)] flex flex-col">
<Navbar />
{isLoading ? (
<main className="flex-1 flex items-center justify-center">
<div className="text-center">
<div className="text-6xl mb-4"></div>
<div className="text-[color:var(--color-text)] text-xl font-semibold mb-2">
Pakli betöltése...
</div>
<div className="text-[color:var(--color-text-muted)]">
Kérlek várj, amíg betöltjük a pakli adatait.
</div>
</div>
</main>
) : (
<main className="flex-1 flex flex-col">
{/* Deck Header */}
<DeckHeader
deck={deck}
onUpdate={handleDeckUpdate}
onSave={handleSaveDeck}
onBack={handleBack}
onDelete={handleDeleteDeck}
/>
{/* Main Content */}
<div className="flex-1 flex">
{/* Left Panel - Cards List */}
<div className="w-80 bg-[color:var(--color-surface)] border-r border-[color:var(--color-surface-selected)] flex flex-col">
<CardsList
cards={deck.cards}
selectedCard={selectedCard}
deckType={deck.type}
onSelectCard={handleSelectCard}
onCreateCard={handleCreateCard}
onDeleteCard={handleDeleteCard}
isCreatingCard={isCreatingCard}
newCardType={newCardType}
/>
</div>
{/* Right Panel - Card Editor */}
<div className="flex-1 bg-[color:var(--color-background)] flex flex-col">
<CardEditor
card={selectedCard}
isCreating={isCreatingCard}
cardType={isCreatingCard ? newCardType : deck.type}
onSave={handleSaveCard}
onCancel={() => {
setIsCreatingCard(false)
setNewCardType(null)
setSelectedCard(null)
}}
/>
</div>
</div>
</main>
)}
{/* Delete Confirmation Modal */}
{showDeleteModal && (
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
<div className="bg-[color:var(--color-surface)] rounded-xl shadow-xl p-6 w-96 text-center animate-fadeIn">
<div className="text-4xl mb-4">🗑</div>
<h3 className="text-lg font-semibold mb-4 text-[color:var(--color-text)]">
Biztosan törölni szeretnéd a(z) "{deck.name}" paklit?
</h3>
<p className="text-sm text-[color:var(--color-text-muted)] mb-6">
Ez a művelet nem visszavonható!
</p>
<div className="flex justify-center gap-4">
<button
onClick={handleConfirmDelete}
className="bg-red-600 text-white px-6 py-2 rounded-lg hover:bg-red-700 transition font-semibold"
>
Igen, törlöm
</button>
<button
onClick={handleCancelDelete}
className="bg-[color:var(--color-background)] text-[color:var(--color-text)] px-6 py-2 rounded-lg hover:bg-[color:var(--color-surface-selected)] transition"
>
Mégse
</button>
</div>
</div>
</div>
)}
</div>
)
}