Deck szerkesztese

This commit is contained in:
2025-10-24 20:34:43 +02:00
parent 8960bd9dce
commit cea9062f91
4 changed files with 223 additions and 67 deletions
@@ -199,12 +199,12 @@ deckRouter.patch('/:id', authRequired, async (req, res) => {
try { try {
const deckId = req.params.id; const deckId = req.params.id;
const userId = (req as any).user.userId; const userId = (req as any).user.userId;
logRequest('Update deck endpoint accessed', req, res, { deckId, userId, updateFields: Object.keys(req.body) });
// Convert string enum values to integers // Convert string enum values to integers
const updateData = convertEnumValues(req.body); const updateData = convertEnumValues(req.body);
const result = await container.updateDeckCommandHandler.execute({ id: deckId, ...updateData }); const result = await container.updateDeckCommandHandler.execute({ id: deckId, userstate: userState, ...updateData });
logRequest('Deck updated successfully', req, res, { deckId, userId }); logRequest('Deck updated successfully', req, res, { deckId, userId });
res.json(result); res.json(result);
@@ -244,7 +244,6 @@ deckRouter.delete('/:id', authRequired, async (req, res) => {
try { try {
const deckId = req.params.id; const deckId = req.params.id;
const userId = (req as any).user.userId; const userId = (req as any).user.userId;
logRequest('Soft delete deck endpoint accessed', req, res, { deckId, userId });
const result = await container.deleteDeckCommandHandler.execute({ id: deckId, soft: true }); const result = await container.deleteDeckCommandHandler.execute({ id: deckId, soft: true });
+24 -2
View File
@@ -20,6 +20,28 @@ export const getDecksPage = async (from = 0, to = 49) => {
} }
} }
export default { // Get a specific deck by ID (authenticated)
createDeck export const getDeckById = async (deckId) => {
try {
const response = await apiClient.get(`/decks/${deckId}`)
return response.data
} catch (err) {
throw err
}
}
// Update an existing deck (authenticated)
export const updateDeck = async (deckId, deck) => {
try {
const response = await apiClient.patch(`/decks/${deckId}`, deck)
return response.data
} catch (err) {
throw err
}
}
export default {
createDeck,
getDeckById,
updateDeck
} }
@@ -1,4 +1,5 @@
import React, { useEffect } from "react" import React, { useEffect } from "react"
import { useNavigate } from "react-router-dom"
import { import {
FaUser, FaUser,
FaLock, FaLock,
@@ -12,11 +13,13 @@ import {
} from "react-icons/fa" } from "react-icons/fa"
export default function DeckInfoPopUp({ deck, onClose }) { export default function DeckInfoPopUp({ deck, onClose }) {
const navigate = useNavigate()
if (!deck) return null if (!deck) return null
// Debug: Log the deck structure to see what we're working with // Debug: Log the deck structure to see what we're working with
console.log('Deck in popup:', deck) console.log('Deck in popup:', deck)
console.log('Cards:', deck.cards) console.log('Raw deck data:', deck.raw)
// Scroll blokkolás amikor a popup nyitva van // Scroll blokkolás amikor a popup nyitva van
useEffect(() => { useEffect(() => {
@@ -29,50 +32,98 @@ export default function DeckInfoPopUp({ deck, onClose }) {
} }
}, []) }, [])
const deckTypes = { // Backend enum mapping
"Luck": { label: "Szerencse", color: "var(--color-luck)" }, const deckTypeMapping = {
"Question": { label: "Kérdés", color: "var(--color-question)" }, 0: { label: "Szerencse", color: "var(--color-luck)" }, // LUCK
"Fun": { label: "Joker", color: "var(--color-fun)" } 1: { label: "Joker", color: "var(--color-fun)" }, // JOKER
2: { label: "Kérdés", color: "var(--color-question)" } // QUESTION
} }
const currentDeckType = deckTypes[deck.type] || { label: deck.type, color: "var(--color-success)" } const ctypeMapping = {
0: "Publikus", // PUBLIC
1: "Privát", // PRIVATE
2: "Vállalati" // ORGANIZATION
}
// Use real deck data with safe fallbacks const stateMapping = {
const creator = deck.creatorName || deck.creator || (deck.user && deck.user.name) || "Ismeretlen" 0: "Aktív", // ACTIVE
const privacy = deck.origin === "Vállalati" || deck.ctype === 1 || deck.privacy === 'public' ? "Publikus" : "Privát" 1: "Törölt" // SOFT_DELETE
}
// Get data from raw if available // Get data from raw (backend data)
const rawData = deck.raw || deck const rawData = deck.raw || deck
// Use played number from raw data for answers count // Type info
const questionsCount = rawData.cardCount || 0 const deckTypeInfo = deckTypeMapping[rawData.type] || { label: "Ismeretlen", color: "var(--color-success)" }
const answersCount = rawData.playedNumber || 0
// Privacy/CType
const privacy = ctypeMapping[rawData.ctype] || "Ismeretlen"
// State
const state = stateMapping[rawData.state] || "Ismeretlen"
// Creator
const creator = rawData.user?.name || rawData.creatorName || rawData.creator || "Ismeretlen"
// Card count
const cardCount = rawData.cardCount || 0
// Played count
const playedNumber = rawData.playedNumber || 0
console.log('Calculated counts:', { questionsCount, answersCount, rawData }) console.log('Mapped data:', {
type: rawData.type,
deckTypeInfo,
ctype: rawData.ctype,
privacy,
state,
cardCount,
playedNumber
})
const mockData = { const mockData = {
...deck, name: rawData.name || deck.name || "Névtelen pakli",
creator, creator,
privacy, privacy,
questionsCount, state,
answersCount questionsCount: cardCount,
answersCount: playedNumber,
created: rawData.creationdate || rawData.created || deck.created || new Date().toISOString(),
description: rawData.description || ""
} }
const formatDate = (dateString) => { const formatDate = (dateString) => {
const date = new Date(dateString) try {
return date.toLocaleDateString('hu-HU', { const date = new Date(dateString)
year: 'numeric', return date.toLocaleDateString('hu-HU', {
month: 'long', year: 'numeric',
day: 'numeric' month: 'long',
}) day: 'numeric'
})
} catch (e) {
return dateString
}
} }
const handleOpenDeck = () => { const handleOpenDeck = () => {
// TODO: Megnyitás funkció - később implementálható
alert("⚠️ A pakli megnyitás funkció még fejlesztés alatt áll!") alert("⚠️ A pakli megnyitás funkció még fejlesztés alatt áll!")
} }
const handleEditDeck = () => { const handleEditDeck = () => {
alert("⚠️ A pakli szerkesztés funkció még fejlesztés alatt áll!") // Get the deck ID from raw data
const deckId = rawData.id || deck.id
if (!deckId) {
alert("⚠️ Hiba: A pakli azonosítója nem található!")
return
}
// Navigate to deck creator with the deck ID
navigate(`/deck-creator/${deckId}`)
// Close the popup
onClose()
} }
return ( return (
@@ -86,7 +137,7 @@ export default function DeckInfoPopUp({ deck, onClose }) {
{/* Header with deck type color */} {/* Header with deck type color */}
<div <div
className="h-2 w-full" className="h-2 w-full"
style={{ backgroundColor: currentDeckType.color }} style={{ backgroundColor: deckTypeInfo.color }}
></div> ></div>
{/* Close button */} {/* Close button */}
@@ -108,13 +159,18 @@ export default function DeckInfoPopUp({ deck, onClose }) {
<span <span
className="inline-block px-3 py-1 rounded-full text-xs font-bold" className="inline-block px-3 py-1 rounded-full text-xs font-bold"
style={{ style={{
background: currentDeckType.color, background: deckTypeInfo.color,
color: "var(--color-text-inverse)", color: "var(--color-text-inverse)",
}} }}
> >
{currentDeckType.label} {deckTypeInfo.label}
</span> </span>
</div> </div>
{mockData.description && (
<p className="text-[color:var(--color-text-muted)] text-sm">
{mockData.description}
</p>
)}
</div> </div>
{/* Data grid */} {/* Data grid */}
@@ -149,13 +205,13 @@ export default function DeckInfoPopUp({ deck, onClose }) {
<div className="flex items-center gap-3 p-3 bg-[color:var(--color-background)]/50 rounded-xl"> <div className="flex items-center gap-3 p-3 bg-[color:var(--color-background)]/50 rounded-xl">
<div <div
className="flex items-center justify-center w-8 h-8 rounded-full" className="flex items-center justify-center w-8 h-8 rounded-full"
style={{ backgroundColor: `${currentDeckType.color}20` }} style={{ backgroundColor: `${deckTypeInfo.color}20` }}
> >
<FaTags style={{ color: currentDeckType.color }} className="text-sm" /> <FaTags style={{ color: deckTypeInfo.color }} className="text-sm" />
</div> </div>
<div> <div>
<div className="text-[color:var(--color-text-muted)] text-xs font-medium">Típus</div> <div className="text-[color:var(--color-text-muted)] text-xs font-medium">Típus</div>
<div className="text-[color:var(--color-text)] font-semibold text-sm">{currentDeckType.label}</div> <div className="text-[color:var(--color-text)] font-semibold text-sm">{deckTypeInfo.label}</div>
</div> </div>
</div> </div>
@@ -172,25 +228,25 @@ export default function DeckInfoPopUp({ deck, onClose }) {
{/* Questions and Answers in one row */} {/* Questions and Answers in one row */}
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
{/* Questions */} {/* Card Count (Kártyák) */}
<div className="flex items-center gap-2 p-3 bg-[color:var(--color-background)]/50 rounded-xl"> <div className="flex items-center gap-2 p-3 bg-[color:var(--color-background)]/50 rounded-xl">
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-[color:var(--color-question)]/20"> <div className="flex items-center justify-center w-8 h-8 rounded-full bg-[color:var(--color-question)]/20">
<FaQuestionCircle className="text-[color:var(--color-question)] text-sm" /> <FaQuestionCircle className="text-[color:var(--color-question)] text-sm" />
</div> </div>
<div> <div>
<div className="text-[color:var(--color-text-muted)] text-xs font-medium">Kérdések</div> <div className="text-[color:var(--color-text-muted)] text-xs font-medium">Kártyák</div>
<div className="text-[color:var(--color-text)] font-semibold">{mockData.questionsCount}</div> <div className="text-[color:var(--color-text)] font-semibold">{mockData.questionsCount}</div>
</div> </div>
</div> </div>
{/* Answers */} {/* Played Count (Játszva) */}
<div className="flex items-center gap-2 p-3 bg-[color:var(--color-background)]/50 rounded-xl"> <div className="flex items-center gap-2 p-3 bg-[color:var(--color-background)]/50 rounded-xl">
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-[color:var(--color-fun)]/20"> <div className="flex items-center justify-center w-8 h-8 rounded-full bg-[color:var(--color-fun)]/20">
<FaComment className="text-[color:var(--color-fun)] text-sm" /> <FaComment className="text-[color:var(--color-fun)] text-sm" />
</div> </div>
<div> <div>
<div className="text-[color:var(--color-text-muted)] text-xs font-medium">Válaszok</div> <div className="text-[color:var(--color-text-muted)] text-xs font-medium">Játszva</div>
<div className="text-[color:var(--color-text)] font-semibold">{mockData.answersCount}</div> <div className="text-[color:var(--color-text)] font-semibold">{mockData.answersCount}×</div>
</div> </div>
</div> </div>
</div> </div>
@@ -7,7 +7,7 @@ import Navbar from "../../components/Navbar/Navbar.jsx"
import DeckHeader from "../../components/DeckCreator/DeckHeader.jsx" import DeckHeader from "../../components/DeckCreator/DeckHeader.jsx"
import CardsList from "../../components/DeckCreator/CardsList.jsx" import CardsList from "../../components/DeckCreator/CardsList.jsx"
import CardEditor from "../../components/DeckCreator/CardEditor.jsx" import CardEditor from "../../components/DeckCreator/CardEditor.jsx"
import { createDeck } from '../../api/deckApi' import { createDeck, getDeckById, updateDeck } from '../../api/deckApi'
import { notifySuccess, notifyError, notifyWarning } from "../../components/Toastify/toastifyServices" import { notifySuccess, notifyError, notifyWarning } from "../../components/Toastify/toastifyServices"
export default function DeckCreator() { export default function DeckCreator() {
@@ -28,6 +28,7 @@ export default function DeckCreator() {
const [selectedCard, setSelectedCard] = useState(null) const [selectedCard, setSelectedCard] = useState(null)
const [isCreatingCard, setIsCreatingCard] = useState(false) const [isCreatingCard, setIsCreatingCard] = useState(false)
const [newCardType, setNewCardType] = useState(null) const [newCardType, setNewCardType] = useState(null)
const [isLoading, setIsLoading] = useState(false)
// Betöltés API-ból // Betöltés API-ból
useEffect(() => { useEffect(() => {
@@ -46,16 +47,44 @@ export default function DeckCreator() {
}, [deckId]) }, [deckId])
const loadDeck = async (id) => { const loadDeck = async (id) => {
// Mock adatok később backendből jön majd setIsLoading(true)
const mockDeck = { try {
id, const deckData = await getDeckById(id)
name: "Demo Deck", console.log('Loaded deck:', deckData)
type: "QUESTION",
privacy: "public", // Type mapping from backend to frontend
description: "Ez egy teszt deck", const typeMapping = {
cards: [] 0: 'LUCK',
1: 'JOKER',
2: 'QUESTION'
}
// CType mapping from backend to frontend
const ctypeMapping = {
0: 'public',
1: 'private',
2: 'organization'
}
setDeck({
id: deckData.id,
name: deckData.name || "Névtelen pakli",
type: typeMapping[deckData.type] || 'QUESTION',
privacy: ctypeMapping[deckData.ctype] || 'private',
description: deckData.description || "",
cards: deckData.cards || [],
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)
} }
setDeck(mockDeck)
} }
const handleDeckUpdate = (updates) => { const handleDeckUpdate = (updates) => {
@@ -71,15 +100,42 @@ export default function DeckCreator() {
'QUESTION': 2 'QUESTION': 2
} }
const ctypeMapping = {
'public': 0,
'private': 1,
'organization': 2
}
const payload = { const payload = {
name: deck.name?.trim() || "Névtelen pakli", name: deck.name?.trim() || "Névtelen pakli",
type: typeMapping[deck.type] ?? 2, type: typeMapping[deck.type] ?? 2,
ctype: deck.privacy === 'public' ? 'PUBLIC' : 'PRIVATE', ctype: ctypeMapping[deck.privacy] ?? 1,
description: deck.description || "",
cards: deck.cards || [] cards: deck.cards || []
} }
// Note: description field is not sent to backend as it's not supported yet
const saved = await createDeck(payload) 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 => ({ setDeck(prev => ({
...prev, ...prev,
id: saved.id ?? prev.id, id: saved.id ?? prev.id,
@@ -88,10 +144,19 @@ export default function DeckCreator() {
})) }))
console.log('Deck saved (backend):', saved) console.log('Deck saved (backend):', saved)
notifySuccess('Pakli sikeresen elmentve!')
} catch (error) { } catch (error) {
console.error('Mentési hiba:', error) console.error('=== DECK SAVE ERROR ===')
notifyError('Hiba történt a mentés során: ' + (error?.response?.data?.error || error.message)) 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)
} }
} }
@@ -193,17 +258,30 @@ export default function DeckCreator() {
<div className="w-full min-h-screen bg-[color:var(--color-background)] flex flex-col"> <div className="w-full min-h-screen bg-[color:var(--color-background)] flex flex-col">
<Navbar /> <Navbar />
<main className="flex-1 flex flex-col"> {isLoading ? (
{/* Deck Header */} <main className="flex-1 flex items-center justify-center">
<DeckHeader <div className="text-center">
deck={deck} <div className="text-6xl mb-4"></div>
onUpdate={handleDeckUpdate} <div className="text-[color:var(--color-text)] text-xl font-semibold mb-2">
onSave={handleSaveDeck} Pakli betöltése...
onBack={handleBack} </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}
/>
{/* Main Content */} {/* Main Content */}
<div className="flex-1 flex"> <div className="flex-1 flex">
{/* Left Panel - Cards List */} {/* Left Panel - Cards List */}
<div className="w-80 bg-[color:var(--color-surface)] border-r border-[color:var(--color-surface-selected)] flex flex-col"> <div className="w-80 bg-[color:var(--color-surface)] border-r border-[color:var(--color-surface-selected)] flex flex-col">
<CardsList <CardsList
@@ -233,7 +311,8 @@ export default function DeckCreator() {
/> />
</div> </div>
</div> </div>
</main> </main>
)}
</div> </div>
) )
} }