From d3dcb7f7dac4c0e87479dd7edb4d00011622acfe Mon Sep 17 00:00:00 2001 From: GitG0r0 <145980798+GitG0r0@users.noreply.github.com> Date: Mon, 27 Oct 2025 18:27:40 +0100 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20Consequence=20kezel=C3=A9s=20=C3=A9s?= =?UTF-8?q?=20deck=20szerkeszt=C3=A9s=20jav=C3=ADt=C3=A1sok?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TaskCardEditor és JokerCardEditor: consequence mezők eltávolítása (csak LuckCardEditor-nél marad) - DeckCreator: kártya type konverzió javítása betöltéskor (szám -> string) - DeckCreator: csak megfelelő típusú kártyák megtartása mentéskor - UpdateDeckCommandHandler: userstate -> authLevel javítás (interface mező helyesen) - sql_schema_only.sql: trigger függvény javítása (NEW.updatedate -> NEW.update_date) --- .../Deck/commands/UpdateDeckCommandHandler.ts | 6 +- SerpentRace_Docker/sql_schema_only.sql | 4 +- .../DeckCreator/JokerCardEditor.jsx | 140 +----------------- .../components/DeckCreator/TaskCardEditor.jsx | 122 --------------- .../src/pages/DeckCreator/DeckCreator.jsx | 95 ++++++++++-- 5 files changed, 91 insertions(+), 276 deletions(-) diff --git a/SerpentRace_Backend/src/Application/Deck/commands/UpdateDeckCommandHandler.ts b/SerpentRace_Backend/src/Application/Deck/commands/UpdateDeckCommandHandler.ts index 8e5b2ca5..85cc4202 100644 --- a/SerpentRace_Backend/src/Application/Deck/commands/UpdateDeckCommandHandler.ts +++ b/SerpentRace_Backend/src/Application/Deck/commands/UpdateDeckCommandHandler.ts @@ -9,12 +9,12 @@ export class UpdateDeckCommandHandler { constructor(private readonly deckRepo: IDeckRepository) {} async execute(cmd: UpdateDeckCommand): Promise { - if(cmd.state !== undefined && cmd.userstate!==1) { + if(cmd.state !== undefined && cmd.authLevel !== 1) { throw new Error('Only admin users can change deck state'); } try { let existingDeck: DeckAggregate | null = null; - if (cmd.userstate === 1) { + if (cmd.authLevel === 1) { existingDeck = await this.deckRepo.findByIdIncludingDeleted(cmd.id); } else { existingDeck = await this.deckRepo.findById(cmd.id); @@ -24,7 +24,7 @@ export class UpdateDeckCommandHandler { throw new Error('Deck not found'); } - if(cmd.authLevel !==1 && existingDeck.userid !== cmd.userid) { + if(cmd.authLevel !== 1 && existingDeck.userid !== cmd.userid) { logAuth(`Unauthorized access attempt to deck with ID: ${cmd.id}, UserID: ${cmd.userid}`); throw new Error('Unauthorized'); } diff --git a/SerpentRace_Docker/sql_schema_only.sql b/SerpentRace_Docker/sql_schema_only.sql index e36d9e25..f915bc4e 100644 --- a/SerpentRace_Docker/sql_schema_only.sql +++ b/SerpentRace_Docker/sql_schema_only.sql @@ -174,11 +174,11 @@ CREATE INDEX "IDX_Games_State" ON "Games" ("state"); CREATE INDEX "IDX_Games_CreatedBy" ON "Games" ("createdBy"); CREATE INDEX "IDX_Games_OrganizationId" ON "Games" ("organizationid"); --- Create update trigger for updatedate columns +-- Create update trigger for update_date columns CREATE OR REPLACE FUNCTION update_updatedate_column() RETURNS TRIGGER AS $$ BEGIN - NEW.updatedate = CURRENT_TIMESTAMP; + NEW.update_date = CURRENT_TIMESTAMP; RETURN NEW; END; $$ language 'plpgsql'; diff --git a/SerpentRace_Frontend/src/components/DeckCreator/JokerCardEditor.jsx b/SerpentRace_Frontend/src/components/DeckCreator/JokerCardEditor.jsx index 5f8118e6..7f87e728 100644 --- a/SerpentRace_Frontend/src/components/DeckCreator/JokerCardEditor.jsx +++ b/SerpentRace_Frontend/src/components/DeckCreator/JokerCardEditor.jsx @@ -4,29 +4,17 @@ import React, { useState, useEffect } from 'react' import { FaTheaterMasks, FaInfoCircle, FaUsers } from 'react-icons/fa' -const consequenceTypes = [ - { value: 0, label: '⬆️ Előre lépés', description: 'A játékos előre lép X mezőt' }, - { value: 1, label: '⬇️ Hátra lépés', description: 'A játékos hátra lép X mezőt' }, - { value: 2, label: '⏸️ Kör kihagyás', description: 'A játékos kihagy egy kört' }, - { value: 3, label: '⏩ Extra kör', description: 'A játékos kap egy extra kört' }, - { value: 5, label: '🏁 Vissza a starthoz', description: 'A játékos visszakerül a starthoz' } -] - export default function JokerCardEditor({ card, onChange }) { const [cardData, setCardData] = useState({ type: 'JOKER', - text: '', - consequence: { type: 0, value: 1 }, - wrongConsequence: { type: 1, value: 1 } + text: '' }) useEffect(() => { if (card) { setCardData({ type: 'JOKER', - text: card.text || '', - consequence: card.consequence || { type: 0, value: 1 }, - wrongConsequence: card.wrongConsequence || { type: 1, value: 1 } + text: card.text || '' }) } }, [card]) @@ -43,36 +31,6 @@ export default function JokerCardEditor({ card, onChange }) { } } - const updateConsequence = (field, value) => { - const newCardData = { - ...cardData, - consequence: { - ...cardData.consequence, - [field]: value - } - } - setCardData(newCardData) - - if (onChange) { - onChange(newCardData) - } - } - - const updateWrongConsequence = (field, value) => { - const newCardData = { - ...cardData, - wrongConsequence: { - ...cardData.wrongConsequence, - [field]: value - } - } - setCardData(newCardData) - - if (onChange) { - onChange(newCardData) - } - } - // Példa joker kártyák const exampleCards = [ "Felelsz vagy mersz? (Az előző játékos kérdez)", @@ -186,100 +144,6 @@ export default function JokerCardEditor({ card, onChange }) { )} - - {/* Következmények (teljesítés esetén) */} -
-

- 🎯 Következmények (teljesítés esetén) -

- -
- {/* Consequence Type */} -
- - -
- {consequenceTypes.find(t => t.value === (cardData.consequence?.type ?? 0))?.description} -
-
- - {/* Consequence Value - csak ha előre/hátra lépés */} - {(cardData.consequence?.type === 0 || cardData.consequence?.type === 1) && ( -
- - updateConsequence('value', parseInt(e.target.value) || 1)} - className="w-full px-4 py-2 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200" - min="1" - max="10" - /> -
- )} -
-
- - {/* Következmények (nem teljesítés esetén) */} -
-

- ❌ Következmények (nem teljesítés esetén) -

- -
- {/* Wrong Consequence Type */} -
- - -
- {consequenceTypes.find(t => t.value === (cardData.wrongConsequence?.type ?? 1))?.description} -
-
- - {/* Wrong Consequence Value - csak ha előre/hátra lépés */} - {(cardData.wrongConsequence?.type === 0 || cardData.wrongConsequence?.type === 1) && ( -
- - updateWrongConsequence('value', parseInt(e.target.value) || 1)} - className="w-full px-4 py-2 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-danger)] focus:border-transparent outline-none transition-all duration-200" - min="1" - max="10" - /> -
- )} -
-
) } \ No newline at end of file diff --git a/SerpentRace_Frontend/src/components/DeckCreator/TaskCardEditor.jsx b/SerpentRace_Frontend/src/components/DeckCreator/TaskCardEditor.jsx index 018e4531..2c12b99f 100644 --- a/SerpentRace_Frontend/src/components/DeckCreator/TaskCardEditor.jsx +++ b/SerpentRace_Frontend/src/components/DeckCreator/TaskCardEditor.jsx @@ -20,14 +20,6 @@ const timeLimits = [ { value: 120, label: '2 perc' } ] -const consequenceTypes = [ - { value: 0, label: '⬆️ Előre lépés', description: 'A játékos előre lép X mezőt' }, - { value: 1, label: '⬇️ Hátra lépés', description: 'A játékos hátra lép X mezőt' }, - { value: 2, label: '⏸️ Kör kihagyás', description: 'A játékos kihagy egy kört' }, - { value: 3, label: '⏩ Extra kör', description: 'A játékos kap egy extra kört' }, - { value: 5, label: '🏁 Vissza a starthoz', description: 'A játékos visszakerül a starthoz' } -] - export default function TaskCardEditor({ card, onChange }) { const updateField = (field, value) => { @@ -92,26 +84,6 @@ export default function TaskCardEditor({ card, onChange }) { onChange({ acceptedAnswers: newAnswers }) } - const updateConsequence = (field, value) => { - const currentConsequence = card.consequence || { type: 0, value: 1 } - onChange({ - consequence: { - ...currentConsequence, - [field]: value - } - }) - } - - const updateWrongConsequence = (field, value) => { - const currentWrongConsequence = card.wrongConsequence || { type: 1, value: 1 } - onChange({ - wrongConsequence: { - ...currentWrongConsequence, - [field]: value - } - }) - } - return (
{/* Feladat típus választó */} @@ -544,100 +516,6 @@ export default function TaskCardEditor({ card, onChange }) {
- - {/* Következmények (helyes válasz esetén) */} -
-

- 🎯 Következmények (helyes válasz esetén) -

- -
- {/* Consequence Type */} -
- - -
- {consequenceTypes.find(t => t.value === (card.consequence?.type ?? 0))?.description} -
-
- - {/* Consequence Value - csak ha előre/hátra lépés */} - {(card.consequence?.type === 0 || card.consequence?.type === 1) && ( -
- - updateConsequence('value', parseInt(e.target.value) || 1)} - className="w-full px-4 py-2 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-success)] focus:border-transparent outline-none transition-all duration-200" - min="1" - max="10" - /> -
- )} -
-
- - {/* Következmények (rossz válasz esetén) */} -
-

- ❌ Következmények (rossz válasz esetén) -

- -
- {/* Wrong Consequence Type */} -
- - -
- {consequenceTypes.find(t => t.value === (card.wrongConsequence?.type ?? 1))?.description} -
-
- - {/* Wrong Consequence Value - csak ha előre/hátra lépés */} - {(card.wrongConsequence?.type === 0 || card.wrongConsequence?.type === 1) && ( -
- - updateWrongConsequence('value', parseInt(e.target.value) || 1)} - className="w-full px-4 py-2 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-danger)] focus:border-transparent outline-none transition-all duration-200" - min="1" - max="10" - /> -
- )} -
-
) } \ No newline at end of file diff --git a/SerpentRace_Frontend/src/pages/DeckCreator/DeckCreator.jsx b/SerpentRace_Frontend/src/pages/DeckCreator/DeckCreator.jsx index feb9a112..44ddc641 100644 --- a/SerpentRace_Frontend/src/pages/DeckCreator/DeckCreator.jsx +++ b/SerpentRace_Frontend/src/pages/DeckCreator/DeckCreator.jsx @@ -66,13 +66,23 @@ export default function DeckCreator() { 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: deckData.cards || [], + cards: processedCards, creationdate: deckData.creationdate, updatedate: deckData.updatedate }) @@ -93,6 +103,71 @@ export default function DeckCreator() { 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, @@ -110,7 +185,7 @@ export default function DeckCreator() { name: deck.name?.trim() || "Névtelen pakli", type: typeMapping[deck.type] ?? 2, ctype: ctypeMapping[deck.privacy] ?? 1, - cards: deck.cards || [] + cards: cleanedCards } // Note: description field is not sent to backend as it's not supported yet @@ -184,17 +259,15 @@ export default function DeckCreator() { return } - const defaultConsequence = { type: 0, value: 1 } - const defaultWrongConsequence = { type: 1, value: 1 } - + // Alapértelmezett consequence csak LUCK típusú kártyákhoz const updatedCard = { ...cardData, - id: isCreatingCard ? Date.now() : cardData.id, - consequence: cardData.consequence || defaultConsequence, - ...(cardData.type === 'QUESTION' || cardData.type === 'JOKER' || cardData.type === 'PAIRING' - ? { wrongConsequence: cardData.wrongConsequence || defaultWrongConsequence } - : {} - ) + 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 From bfcdd3ec9d11567876e395578193f32e0112832a Mon Sep 17 00:00:00 2001 From: GitG0r0 <145980798+GitG0r0@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:25:25 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Deck=20t=C3=B6rl=C3=A9s=20funkci=C3=B3=20im?= =?UTF-8?q?plement=C3=A1l=C3=A1sa=20modal-lal=20=C3=A9s=20consequence=20?= =?UTF-8?q?=C3=A9rt=C3=A9kek=20finom=C3=ADt=C3=A1sa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SerpentRace_Frontend/src/api/deckApi.js | 13 +++- .../src/components/DeckCreator/DeckHeader.jsx | 30 ++++++--- .../components/DeckCreator/LuckCardEditor.jsx | 11 ++-- .../src/pages/DeckCreator/DeckCreator.jsx | 63 ++++++++++++++++++- 4 files changed, 102 insertions(+), 15 deletions(-) diff --git a/SerpentRace_Frontend/src/api/deckApi.js b/SerpentRace_Frontend/src/api/deckApi.js index 77dbf91e..1a9460a7 100644 --- a/SerpentRace_Frontend/src/api/deckApi.js +++ b/SerpentRace_Frontend/src/api/deckApi.js @@ -40,8 +40,19 @@ export const updateDeck = async (deckId, deck) => { } } +// Delete a deck (soft delete) (authenticated) +export const deleteDeck = async (deckId) => { + try { + const response = await apiClient.delete(`/decks/${deckId}`) + return response.data + } catch (err) { + throw err + } +} + export default { createDeck, getDeckById, - updateDeck + updateDeck, + deleteDeck } diff --git a/SerpentRace_Frontend/src/components/DeckCreator/DeckHeader.jsx b/SerpentRace_Frontend/src/components/DeckCreator/DeckHeader.jsx index b285dd5e..83f7dbd4 100644 --- a/SerpentRace_Frontend/src/components/DeckCreator/DeckHeader.jsx +++ b/SerpentRace_Frontend/src/components/DeckCreator/DeckHeader.jsx @@ -2,7 +2,7 @@ // Deck alapadatok szerkesztése és mentés import React, { useState, useRef, useEffect } from "react" -import { FaSave, FaArrowLeft, FaGlobe, FaLock, FaQuestionCircle, FaDice, FaLaughBeam } from "react-icons/fa" +import { FaSave, FaArrowLeft, FaGlobe, FaLock, FaQuestionCircle, FaDice, FaLaughBeam, FaTrash } from "react-icons/fa" const deckTypes = [ { value: "QUESTION", label: "Kérdés", icon: FaQuestionCircle, color: "var(--color-question)" }, @@ -15,7 +15,7 @@ const privacyOptions = [ { value: "public", label: "Publikus", icon: FaGlobe } ] -export default function DeckHeader({ deck, onUpdate, onSave, onBack }) { +export default function DeckHeader({ deck, onUpdate, onSave, onBack, onDelete }) { const [isTypeDropdownOpen, setIsTypeDropdownOpen] = useState(false); const [isPrivacyDropdownOpen, setIsPrivacyDropdownOpen] = useState(false); const typeDropdownRef = useRef(null); @@ -64,13 +64,25 @@ export default function DeckHeader({ deck, onUpdate, onSave, onBack }) { - +
+ {deck.id && ( + + )} + + +
{/* Main Content Row */} diff --git a/SerpentRace_Frontend/src/components/DeckCreator/LuckCardEditor.jsx b/SerpentRace_Frontend/src/components/DeckCreator/LuckCardEditor.jsx index f59fcb76..e035aa79 100644 --- a/SerpentRace_Frontend/src/components/DeckCreator/LuckCardEditor.jsx +++ b/SerpentRace_Frontend/src/components/DeckCreator/LuckCardEditor.jsx @@ -150,20 +150,23 @@ export default function LuckCardEditor({ card, onChange }) { - {/* Consequence Value - csak ha előre/hátra lépés */} - {(cardData.consequence?.type === 0 || cardData.consequence?.type === 1) && ( + {/* Consequence Value - csak kör kihagyás és extra kör */} + {(cardData.consequence?.type === 2 || cardData.consequence?.type === 3) && (
updateConsequence('value', parseInt(e.target.value) || 1)} className="w-full px-4 py-2 rounded-xl bg-[color:var(--color-background)] border border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-luck)] focus:border-transparent outline-none transition-all duration-200" /> +
+ Érték: 1-5 között +
)} diff --git a/SerpentRace_Frontend/src/pages/DeckCreator/DeckCreator.jsx b/SerpentRace_Frontend/src/pages/DeckCreator/DeckCreator.jsx index 44ddc641..2f155388 100644 --- a/SerpentRace_Frontend/src/pages/DeckCreator/DeckCreator.jsx +++ b/SerpentRace_Frontend/src/pages/DeckCreator/DeckCreator.jsx @@ -7,7 +7,7 @@ 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 } from '../../api/deckApi' +import { createDeck, getDeckById, updateDeck, deleteDeck } from '../../api/deckApi' import { notifySuccess, notifyError, notifyWarning } from "../../components/Toastify/toastifyServices" export default function DeckCreator() { @@ -29,6 +29,7 @@ export default function DeckCreator() { 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(() => { @@ -239,6 +240,36 @@ export default function DeckCreator() { 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) @@ -351,6 +382,7 @@ export default function DeckCreator() { onUpdate={handleDeckUpdate} onSave={handleSaveDeck} onBack={handleBack} + onDelete={handleDeleteDeck} /> {/* Main Content */} @@ -386,6 +418,35 @@ export default function DeckCreator() { )} + + {/* Delete Confirmation Modal */} + {showDeleteModal && ( +
+
+
🗑️
+

+ Biztosan törölni szeretnéd a(z) "{deck.name}" paklit? +

+

+ Ez a művelet nem visszavonható! +

+
+ + +
+
+
+ )} ) }