javitasok-plusz #81

Merged
Donat merged 3 commits from javitasok-plusz into main 2025-10-30 18:43:41 +01:00
5 changed files with 91 additions and 276 deletions
Showing only changes of commit d3dcb7f7da - Show all commits
@@ -9,12 +9,12 @@ export class UpdateDeckCommandHandler {
constructor(private readonly deckRepo: IDeckRepository) {} constructor(private readonly deckRepo: IDeckRepository) {}
async execute(cmd: UpdateDeckCommand): Promise<ShortDeckDto | null> { async execute(cmd: UpdateDeckCommand): Promise<ShortDeckDto | null> {
if(cmd.state !== undefined && cmd.userstate!==1) { if(cmd.state !== undefined && cmd.authLevel !== 1) {
throw new Error('Only admin users can change deck state'); throw new Error('Only admin users can change deck state');
} }
try { try {
let existingDeck: DeckAggregate | null = null; let existingDeck: DeckAggregate | null = null;
if (cmd.userstate === 1) { if (cmd.authLevel === 1) {
existingDeck = await this.deckRepo.findByIdIncludingDeleted(cmd.id); existingDeck = await this.deckRepo.findByIdIncludingDeleted(cmd.id);
} else { } else {
existingDeck = await this.deckRepo.findById(cmd.id); existingDeck = await this.deckRepo.findById(cmd.id);
+2 -2
View File
@@ -174,11 +174,11 @@ CREATE INDEX "IDX_Games_State" ON "Games" ("state");
CREATE INDEX "IDX_Games_CreatedBy" ON "Games" ("createdBy"); CREATE INDEX "IDX_Games_CreatedBy" ON "Games" ("createdBy");
CREATE INDEX "IDX_Games_OrganizationId" ON "Games" ("organizationid"); 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() CREATE OR REPLACE FUNCTION update_updatedate_column()
RETURNS TRIGGER AS $$ RETURNS TRIGGER AS $$
BEGIN BEGIN
NEW.updatedate = CURRENT_TIMESTAMP; NEW.update_date = CURRENT_TIMESTAMP;
RETURN NEW; RETURN NEW;
END; END;
$$ language 'plpgsql'; $$ language 'plpgsql';
@@ -4,29 +4,17 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect } from 'react'
import { FaTheaterMasks, FaInfoCircle, FaUsers } from 'react-icons/fa' 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 }) { export default function JokerCardEditor({ card, onChange }) {
const [cardData, setCardData] = useState({ const [cardData, setCardData] = useState({
type: 'JOKER', type: 'JOKER',
text: '', text: ''
consequence: { type: 0, value: 1 },
wrongConsequence: { type: 1, value: 1 }
}) })
useEffect(() => { useEffect(() => {
if (card) { if (card) {
setCardData({ setCardData({
type: 'JOKER', type: 'JOKER',
text: card.text || '', text: card.text || ''
consequence: card.consequence || { type: 0, value: 1 },
wrongConsequence: card.wrongConsequence || { type: 1, value: 1 }
}) })
} }
}, [card]) }, [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 // Példa joker kártyák
const exampleCards = [ const exampleCards = [
"Felelsz vagy mersz? (Az előző játékos kérdez)", "Felelsz vagy mersz? (Az előző játékos kérdez)",
@@ -186,100 +144,6 @@ export default function JokerCardEditor({ card, onChange }) {
</div> </div>
)} )}
</div> </div>
{/* Következmények (teljesítés esetén) */}
<div className="bg-[color:var(--color-surface)] rounded-xl p-6">
<h3 className="text-lg font-semibold text-[color:var(--color-text)] mb-4">
🎯 Következmények (teljesítés esetén)
</h3>
<div className="space-y-4">
{/* Consequence Type */}
<div>
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
Hatás típusa
</label>
<select
value={cardData.consequence?.type ?? 0}
onChange={(e) => updateConsequence('type', parseInt(e.target.value))}
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"
>
{consequenceTypes.map(type => (
<option key={type.value} value={type.value}>
{type.label}
</option>
))}
</select>
<div className="text-xs text-[color:var(--color-text-muted)] mt-1">
{consequenceTypes.find(t => t.value === (cardData.consequence?.type ?? 0))?.description}
</div>
</div>
{/* Consequence Value - csak ha előre/hátra lépés */}
{(cardData.consequence?.type === 0 || cardData.consequence?.type === 1) && (
<div>
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
Mezők száma
</label>
<input
type="number"
value={cardData.consequence?.value ?? 1}
onChange={(e) => 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"
/>
</div>
)}
</div>
</div>
{/* Következmények (nem teljesítés esetén) */}
<div className="bg-[color:var(--color-surface)] rounded-xl p-6">
<h3 className="text-lg font-semibold text-[color:var(--color-text)] mb-4">
Következmények (nem teljesítés esetén)
</h3>
<div className="space-y-4">
{/* Wrong Consequence Type */}
<div>
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
Hatás típusa
</label>
<select
value={cardData.wrongConsequence?.type ?? 1}
onChange={(e) => updateWrongConsequence('type', parseInt(e.target.value))}
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"
>
{consequenceTypes.map(type => (
<option key={type.value} value={type.value}>
{type.label}
</option>
))}
</select>
<div className="text-xs text-[color:var(--color-text-muted)] mt-1">
{consequenceTypes.find(t => t.value === (cardData.wrongConsequence?.type ?? 1))?.description}
</div>
</div>
{/* Wrong Consequence Value - csak ha előre/hátra lépés */}
{(cardData.wrongConsequence?.type === 0 || cardData.wrongConsequence?.type === 1) && (
<div>
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
Mezők száma
</label>
<input
type="number"
value={cardData.wrongConsequence?.value ?? 1}
onChange={(e) => 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"
/>
</div>
)}
</div>
</div>
</div> </div>
) )
} }
@@ -20,14 +20,6 @@ const timeLimits = [
{ value: 120, label: '2 perc' } { 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 }) { export default function TaskCardEditor({ card, onChange }) {
const updateField = (field, value) => { const updateField = (field, value) => {
@@ -92,26 +84,6 @@ export default function TaskCardEditor({ card, onChange }) {
onChange({ acceptedAnswers: newAnswers }) 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 ( return (
<div className="max-w-4xl mx-auto space-y-6"> <div className="max-w-4xl mx-auto space-y-6">
{/* Feladat típus választó */} {/* Feladat típus választó */}
@@ -544,100 +516,6 @@ export default function TaskCardEditor({ card, onChange }) {
</div> </div>
</div> </div>
</div> </div>
{/* Következmények (helyes válasz esetén) */}
<div className="bg-[color:var(--color-surface)] rounded-xl p-6">
<h3 className="text-lg font-semibold text-[color:var(--color-text)] mb-4">
🎯 Következmények (helyes válasz esetén)
</h3>
<div className="space-y-4">
{/* Consequence Type */}
<div>
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
Hatás típusa
</label>
<select
value={card.consequence?.type ?? 0}
onChange={(e) => updateConsequence('type', parseInt(e.target.value))}
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"
>
{consequenceTypes.map(type => (
<option key={type.value} value={type.value}>
{type.label}
</option>
))}
</select>
<div className="text-xs text-[color:var(--color-text-muted)] mt-1">
{consequenceTypes.find(t => t.value === (card.consequence?.type ?? 0))?.description}
</div>
</div>
{/* Consequence Value - csak ha előre/hátra lépés */}
{(card.consequence?.type === 0 || card.consequence?.type === 1) && (
<div>
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
Mezők száma
</label>
<input
type="number"
value={card.consequence?.value ?? 1}
onChange={(e) => 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"
/>
</div>
)}
</div>
</div>
{/* Következmények (rossz válasz esetén) */}
<div className="bg-[color:var(--color-surface)] rounded-xl p-6">
<h3 className="text-lg font-semibold text-[color:var(--color-text)] mb-4">
Következmények (rossz válasz esetén)
</h3>
<div className="space-y-4">
{/* Wrong Consequence Type */}
<div>
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
Hatás típusa
</label>
<select
value={card.wrongConsequence?.type ?? 1}
onChange={(e) => updateWrongConsequence('type', parseInt(e.target.value))}
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"
>
{consequenceTypes.map(type => (
<option key={type.value} value={type.value}>
{type.label}
</option>
))}
</select>
<div className="text-xs text-[color:var(--color-text-muted)] mt-1">
{consequenceTypes.find(t => t.value === (card.wrongConsequence?.type ?? 1))?.description}
</div>
</div>
{/* Wrong Consequence Value - csak ha előre/hátra lépés */}
{(card.wrongConsequence?.type === 0 || card.wrongConsequence?.type === 1) && (
<div>
<label className="block text-[color:var(--color-text-muted)] text-sm font-medium mb-2">
Mezők száma
</label>
<input
type="number"
value={card.wrongConsequence?.value ?? 1}
onChange={(e) => 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"
/>
</div>
)}
</div>
</div>
</div> </div>
) )
} }
@@ -66,13 +66,23 @@ export default function DeckCreator() {
2: 'organization' 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({ setDeck({
id: deckData.id, id: deckData.id,
name: deckData.name || "Névtelen pakli", name: deckData.name || "Névtelen pakli",
type: typeMapping[deckData.type] || 'QUESTION', type: typeMapping[deckData.type] || 'QUESTION',
privacy: ctypeMapping[deckData.ctype] || 'private', privacy: ctypeMapping[deckData.ctype] || 'private',
description: deckData.description || "", description: deckData.description || "",
cards: deckData.cards || [], cards: processedCards,
creationdate: deckData.creationdate, creationdate: deckData.creationdate,
updatedate: deckData.updatedate updatedate: deckData.updatedate
}) })
@@ -93,6 +103,71 @@ export default function DeckCreator() {
const handleSaveDeck = async () => { const handleSaveDeck = async () => {
try { 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 // Típus konverzió backendhez
const typeMapping = { const typeMapping = {
'LUCK': 0, 'LUCK': 0,
@@ -110,7 +185,7 @@ export default function DeckCreator() {
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: ctypeMapping[deck.privacy] ?? 1, ctype: ctypeMapping[deck.privacy] ?? 1,
cards: deck.cards || [] cards: cleanedCards
} }
// Note: description field is not sent to backend as it's not supported yet // Note: description field is not sent to backend as it's not supported yet
@@ -184,17 +259,15 @@ export default function DeckCreator() {
return return
} }
const defaultConsequence = { type: 0, value: 1 } // Alapértelmezett consequence csak LUCK típusú kártyákhoz
const defaultWrongConsequence = { type: 1, value: 1 }
const updatedCard = { const updatedCard = {
...cardData, ...cardData,
id: isCreatingCard ? Date.now() : cardData.id, id: isCreatingCard ? Date.now() : cardData.id
consequence: cardData.consequence || defaultConsequence, }
...(cardData.type === 'QUESTION' || cardData.type === 'JOKER' || cardData.type === 'PAIRING'
? { wrongConsequence: cardData.wrongConsequence || defaultWrongConsequence } // 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 let wasInvalidCardDeleted = false