453 lines
15 KiB
React
453 lines
15 KiB
React
// 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>
|
||
)
|
||
}
|