Files
SerpentRace/SerpentRace_Frontend/src/components/DeckCreator/DeckManager.jsx
T
GitG0r0 edca8f84cd Fix: Kártya típus kezelés javítása és joker kártyák megjelenítése
- Hozzáadva react-toastify a notifyWarning használatához
- Javítva a CardEditor fejléc hogy helyesen jelenítse meg az új kártya típusát
- Javítva a CardsList 'szerkesztés folyamatban' rész hogy QUESTION/JOKER/LUCK értékeket használjon
- Implementálva az automatikus nem megfelelő típusú kártyák törlése új kártya mentésekor
- Hozzáadva hibakezelés a kártya mentési logikához
- Joker típus címke változtatva 'Szórakozás'-ról 'Joker'-re
- Joker kártya szín változtatva citromsárgára (#FFD700)
- Docker watch mode volume konfiguráció javítása a hot reload-hoz
2025-10-22 23:21:19 +02:00

329 lines
13 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from "react"
import { useNavigate } from "react-router-dom"
import {
FaPlus,
FaFilter,
FaCalendarAlt,
FaArrowUp,
FaArrowDown,
FaSortAlphaDown,
FaSortAlphaUp,
FaQuestionCircle,
} from "react-icons/fa"
import SearchBox from "../Search/SearchBox"
import PopUp from "../PopUp/PopUp"
import DeckInfoPopUp from "../PopUp/DeckInfoPopUp"
const deckTypes = [
{ label: "Luck", color: "var(--color-luck)" },
{ label: "Question", color: "var(--color-question)" },
{ label: "Joker", color: "var(--color-fun)" },
]
// initial state will be fetched from backend
const origins = ["Mind", "Vállalati", "Saját"]
const sortOptions = [
{
value: "date-asc",
label: (
<>
<FaCalendarAlt className="inline mr-1" />
<FaArrowUp className="inline" />
</>
),
},
{
value: "date-desc",
label: (
<>
<FaCalendarAlt className="inline mr-1" />
<FaArrowDown className="inline" />
</>
),
},
{
value: "abc-asc",
label: (
<>
<FaSortAlphaDown className="inline" />
</>
),
},
{
value: "abc-desc",
label: (
<>
<FaSortAlphaUp className="inline" />
</>
),
},
]
const DeckManager = () => {
const navigate = useNavigate()
const [selectedType, setSelectedType] = useState("All")
const [selectedOrigin, setSelectedOrigin] = useState("Mind")
const [sortBy, setSortBy] = useState("date-desc")
const [search, setSearch] = useState("")
const [showSortHelp, setShowSortHelp] = useState(false)
const [selectedDeck, setSelectedDeck] = useState(null)
const [decks, setDecks] = useState([])
const [loading, setLoading] = useState(false)
useEffect(() => {
let mounted = true
const load = async () => {
setLoading(true)
try {
const result = await import('../../api/deckApi').then(m => m.getDecksPage(0, 49))
if (!mounted) return
// 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',
created: d.creationdate ? new Date(d.creationdate).toLocaleDateString() : '',
origin: d.ctype === 2 ? 'Vállalati' : d.ctype === 0 ? 'Mind' : 'Saját',
raw: d
}))
setDecks(mapped)
} catch (err) {
console.error('Failed to load decks', err)
} finally {
setLoading(false)
}
}
load()
return () => { mounted = false }
}, [])
// Filter logic
const sourceDecks = decks
let filteredDecks = sourceDecks.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())
return typeMatch && originMatch && searchMatch
})
// Sort logic
filteredDecks = [...filteredDecks].sort((a, b) => {
if (sortBy === "date-asc") {
return a.created.localeCompare(b.created)
} else if (sortBy === "date-desc") {
return b.created.localeCompare(a.created)
} else if (sortBy === "abc-asc") {
return a.name.localeCompare(b.name)
} else if (sortBy === "abc-desc") {
return b.name.localeCompare(a.name)
}
return 0
})
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">
{/* Filters */}
<div className="flex flex-col md:flex-row gap-3 justify-between items-center mb-10 bg-[color:var(--color-surface)]/80 backdrop-blur-lg rounded-2xl px-6 py-4 shadow-lg">
<div className="flex gap-2 items-center w-full md:w-auto">
<SearchBox
value={search}
onChange={(e) => setSearch(e.target.value)}
width={240}
placeholder="Keresés..."
className="mr-4"
/>
<FaFilter style={{ color: "var(--color-success)" }} className="mr-2" />
<span className="text-[color:var(--color-text)] font-semibold mr-2">Típus:</span>
<button
className={`px-3 py-1 rounded-lg font-medium transition-all duration-200 ${
selectedType === "All"
? "bg-[color:var(--color-surface-selected)] text-[color:var(--color-text)] border border-[color:var(--color-surface)]"
: "text-[color:var(--color-text)] bg-[color:var(--color-success)]/10 hover:bg-[color:var(--color-success)]/30"
}`}
onClick={() => setSelectedType("All")}
>
Mind
</button>
{deckTypes.map((type) => (
<button
key={type.label}
className={`px-3 py-1 rounded-lg font-medium transition-all duration-200 ml-1 ${
selectedType === type.label
? "text-[color:var(--color-text-inverse)] border border-[color:var(--color-surface)]"
: "text-[color:var(--color-text)] bg-[color:var(--color-success)]/10 hover:bg-[color:var(--color-success)]/30"
}`}
style={selectedType === type.label ? { background: type.color } : undefined}
onClick={() => setSelectedType(type.label)}
>
{type.label === "Luck"
? "Szerencse"
: type.label === "Question"
? "Kérdés"
: type.label === "Joker"
? "Joker"
: type.label}
</button>
))}
<span className="text-[color:var(--color-text)] font-semibold mr-2 ml-2">Eredet:</span>
<select
className="px-3 py-1 rounded-lg bg-[color:var(--color-success)]/10 hover:bg-[color:var(--color-success)]/30 text-[color:var(--color-text)] border-none focus:ring-2 focus:ring-[color:var(--color-success)] outline-none"
style={{ backgroundColor: "rgba(0, 255, 0, 0.10)" }}
value={selectedOrigin}
onChange={(e) => setSelectedOrigin(e.target.value)}
>
{origins.map((origin) => (
<option
key={origin}
value={origin}
style={{ backgroundColor: "var(--color-surface)", color: "var(--color-text)" }}
>
{origin === "Mind"
? "Mind"
: origin === "Vállalati"
? "Vállalati"
: origin === "Saját"
? "Saját"
: origin}
</option>
))}
</select>
<span className="text-[color:var(--color-text)] font-semibold mr-2 ml-2 flex items-center gap-1">
Rendezés:
<button
type="button"
className="ml-1 text-[color:var(--color-success)] hover:text-[color:var(--color-text)] focus:outline-none"
onClick={() => setShowSortHelp(true)}
aria-label="Rendezési magyarázat megnyitása"
style={{ fontSize: 18, lineHeight: 1 }}
>
<FaQuestionCircle />
</button>
</span>
<select
className="px-3 py-1 rounded-lg bg-[color:var(--color-success)]/10 hover:bg-[color:var(--color-success)]/30 text-[color:var(--color-text)] border-none focus:ring-2 focus:ring-[color:var(--color-success)] outline-none flex items-center"
style={{ backgroundColor: "rgba(0, 255, 0, 0.10)" }}
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
aria-label="Rendezés"
>
<option
value="date-asc"
style={{ backgroundColor: "var(--color-surface)", color: "var(--color-text)" }}
>
📅
</option>
<option
value="date-desc"
style={{ backgroundColor: "var(--color-surface)", color: "var(--color-text)" }}
>
📅
</option>
<option
value="abc-asc"
style={{ backgroundColor: "var(--color-surface)", color: "var(--color-text)" }}
>
AZ
</option>
<option
value="abc-desc"
style={{ backgroundColor: "var(--color-surface)", color: "var(--color-text)" }}
>
ZA
</option>
</select>
{showSortHelp && (
<PopUp onClose={() => setShowSortHelp(false)}>
<h2 className="text-lg font-bold mb-4">Rendezési lehetőségek magyarázata</h2>
<ul className="space-y-2 text-[color:var(--color-night)]">
<li>
<span className="font-bold">📅</span> Dátum szerint növekvő sorrendben (legrégebbi
elöl)
</li>
<li>
<span className="font-bold">📅</span> Dátum szerint csökkenő sorrendben (legújabb elöl)
</li>
<li>
<span className="font-bold">AZ</span> Név szerint növekvő sorrendben (A-tól Z-ig)
</li>
<li>
<span className="font-bold">ZA</span> Név szerint csökkenő sorrendben (Z-től A-ig)
</li>
</ul>
<button
className="mt-6 px-4 py-2 rounded-lg bg-[color:var(--color-success)] text-[color:var(--color-text-inverse)] font-semibold hover:bg-[color:var(--color-success)]/80 transition"
onClick={() => setShowSortHelp(false)}
>
Bezárás
</button>
</PopUp>
)}
</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) */}
<div
onClick={() => navigate("/deck-creator")}
className="flex flex-col items-center justify-center h-48 bg-[color:var(--color-card)] border-2 border-dashed border-[color:var(--color-success)] rounded-2xl cursor-pointer hover:bg-[color:var(--color-success)]/20 transition-all duration-200 shadow-lg"
>
<FaPlus style={{ color: "var(--color-success)" }} className="text-5xl mb-2" />
<span className="text-[color:var(--color-text)] font-semibold">Új pakli létrehozása</span>
</div>
{/* Existing Decks (from backend) */}
{loading && (
<div className="col-span-full text-center text-[color:var(--color-text-muted)]">Betöltés...</div>
)}
{!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) => {
const deckType = deckTypes.find((t) => t.label === deck.type)
const borderColor = deckType ? deckType.color : "var(--color-success)"
return (
<div
key={deck.id}
className="flex flex-col justify-between h-48 bg-[color:var(--color-card)] rounded-2xl p-6 shadow-lg border-t-4 hover:scale-105 transition-transform duration-200 cursor-pointer"
style={{ borderTopColor: borderColor }}
onClick={() => setSelectedDeck(deck)}
>
<div>
<span
className="inline-block px-3 py-1 rounded-full text-xs font-bold mb-2"
style={{
background: deckType?.color,
color: "var(--color-text-inverse)",
}}
>
{deck.type === "Luck"
? "Szerencse"
: deck.type === "Question"
? "Kérdés"
: deck.type === "Fun"
? "Joker"
: deck.type}
</span>
<h2 className="text-xl font-bold text-[color:var(--color-text)] mb-1 truncate">
{deck.name}
</h2>
</div>
<div className="text-[color:var(--color-text-muted)] text-sm mt-2">
Létrehozva: {deck.created}
</div>
</div>
)
})}
</div>
</div>
{/* Deck Info Popup */}
{selectedDeck && <DeckInfoPopUp deck={selectedDeck} onClose={() => setSelectedDeck(null)} />}
</div>
)
}
export default DeckManager