This commit is contained in:
Dmitry 2026-01-11 00:42:10 +03:00
parent 13ecd4ff57
commit 625c5837ee
4 changed files with 161 additions and 110 deletions

View file

@ -739,8 +739,12 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect, On
return; return;
} }
try {
// Убеждаемся, что questions - это массив
const questionsArray = Array.isArray(payload.questions) ? payload.questions : [];
// Обновляем вопросы через service (который добавит UUID если нужно) // Обновляем вопросы через service (который добавит UUID если нужно)
await this.roomsService.updateRoomPack(payload.roomId, payload.questions); await this.roomsService.updateRoomPack(payload.roomId, questionsArray);
// После обновления вопросов проверяем и обновляем currentQuestionId // После обновления вопросов проверяем и обновляем currentQuestionId
const room = (await this.prisma.room.findUnique({ const room = (await this.prisma.room.findUnique({
@ -783,6 +787,10 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect, On
} }
await this.broadcastFullState(payload.roomCode); await this.broadcastFullState(payload.roomCode);
} catch (error: any) {
console.error('Error updating room pack:', error);
client.emit('error', { message: error.message || 'Failed to update questions' });
}
} }
@SubscribeMessage('importQuestions') @SubscribeMessage('importQuestions')
@ -810,8 +818,13 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect, On
return; return;
} }
try {
await this.roomPackService.importQuestions(payload.roomId, payload.sourcePackId, payload.questionIndices); await this.roomPackService.importQuestions(payload.roomId, payload.sourcePackId, payload.questionIndices);
await this.broadcastFullState(payload.roomCode); await this.broadcastFullState(payload.roomCode);
} catch (error: any) {
console.error('Error importing questions:', error);
client.emit('error', { message: error.message || 'Failed to import questions' });
}
} }
@SubscribeMessage('kickPlayer') @SubscribeMessage('kickPlayer')

View file

@ -1,6 +1,8 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { questionsApi } from '../services/api' import { questionsApi } from '../services/api'
import { useTheme } from '../context/ThemeContext' import { useTheme } from '../context/ThemeContext'
import { useAuth } from '../context/AuthContext'
import socketService from '../services/socket'
import './GameManagementModal.css' import './GameManagementModal.css'
import './QuestionsModal.css' import './QuestionsModal.css'
@ -37,6 +39,7 @@ const GameManagementModal = ({
onAddPlayer, onAddPlayer,
}) => { }) => {
const { currentThemeData } = useTheme() const { currentThemeData } = useTheme()
const { user } = useAuth()
const [activeTab, setActiveTab] = useState(initialTab) // players | game | scoring | questions const [activeTab, setActiveTab] = useState(initialTab) // players | game | scoring | questions
const [selectedPlayer, setSelectedPlayer] = useState(null) const [selectedPlayer, setSelectedPlayer] = useState(null)
const [customPoints, setCustomPoints] = useState(10) const [customPoints, setCustomPoints] = useState(10)
@ -96,6 +99,24 @@ const GameManagementModal = ({
} }
}, [isOpen, initialTab]) }, [isOpen, initialTab])
// Обработка WebSocket ошибок для операций с вопросами
useEffect(() => {
if (!isOpen) return
const handleError = (error) => {
console.error('WebSocket error in GameManagementModal:', error)
if (error.message) {
setJsonError(error.message)
}
}
socketService.on('error', handleError)
return () => {
socketService.off('error', handleError)
}
}, [isOpen])
if (!isOpen) return null if (!isOpen) return null
const gameStatus = room?.status || 'WAITING' const gameStatus = room?.status || 'WAITING'
@ -256,13 +277,16 @@ const GameManagementModal = ({
})), })),
} }
// Убеждаемся, что questions - это массив
const currentQuestions = Array.isArray(questions) ? questions : []
let updatedQuestions let updatedQuestions
if (editingQuestion) { if (editingQuestion) {
updatedQuestions = questions.map(q => updatedQuestions = currentQuestions.map(q =>
q.id === editingQuestion.id ? questionData : q q.id === editingQuestion.id ? questionData : q
) )
} else { } else {
updatedQuestions = [...questions, questionData] updatedQuestions = [...currentQuestions, questionData]
} }
onUpdateQuestions(updatedQuestions) onUpdateQuestions(updatedQuestions)
@ -577,27 +601,35 @@ const GameManagementModal = ({
} }
const handleImportSelected = () => { const handleImportSelected = () => {
if (!room?.id || !room?.code || !user?.id || !selectedPack) {
setJsonError('Ошибка: недостаточно данных для импорта')
return
}
const indices = Array.from(selectedQuestionIndices) const indices = Array.from(selectedQuestionIndices)
const questionsToImport = indices.map(idx => packQuestions[idx]).filter(Boolean) if (indices.length === 0) {
setJsonError('Выберите хотя бы один вопрос для импорта')
return
}
const copiedQuestions = questionsToImport.map((q) => ({ try {
id: crypto.randomUUID(), socketService.importQuestions(
text: q.text || '', room.id,
answers: (q.answers || []).map(a => ({ room.code,
id: a.id || crypto.randomUUID(), user.id,
text: a.text, selectedPack,
points: a.points indices
})), )
}))
const updatedQuestions = [...questions, ...copiedQuestions]
onUpdateQuestions(updatedQuestions)
setSelectedQuestionIndices(new Set()) setSelectedQuestionIndices(new Set())
setSearchQuery('') setSearchQuery('')
setShowPackImportModal(false) setShowPackImportModal(false)
setJsonError('') setJsonError('')
alert(`Импортировано ${copiedQuestions.length} вопросов`) // Уведомление будет показано через WebSocket событие gameStateUpdated
} catch (error) {
console.error('Error importing questions:', error)
setJsonError('Ошибка при импорте вопросов: ' + (error.message || 'Неизвестная ошибка'))
}
} }
return ( return (

View file

@ -46,7 +46,9 @@ const GamePage = () => {
// Храним participantId текущего пользователя для проверки удаления // Храним participantId текущего пользователя для проверки удаления
const currentUserParticipantIdRef = useRef(null); const currentUserParticipantIdRef = useRef(null);
// Храним предыдущий themeId комнаты для отслеживания изменений // Храним предыдущий themeId комнаты для отслеживания изменений
const previousThemeIdRef = useRef(null); // Используем undefined для обозначения первой загрузки (не установлено),
// null означает "тема не установлена в комнате"
const previousThemeIdRef = useRef(undefined);
// ЕДИНСТВЕННЫЙ обработчик состояния игры // ЕДИНСТВЕННЫЙ обработчик состояния игры
useEffect(() => { useEffect(() => {
@ -65,14 +67,15 @@ const GamePage = () => {
currentUserParticipantIdRef.current = currentUserParticipant?.id || null; currentUserParticipantIdRef.current = currentUserParticipant?.id || null;
} }
// Применяем тему комнаты, если она изменилась // Применяем тему комнаты при первой загрузке или если она изменилась
const currentThemeId = state.themeId || null; const currentThemeId = state.themeId || null;
if (currentThemeId !== previousThemeIdRef.current) { const isFirstLoad = previousThemeIdRef.current === undefined;
if (isFirstLoad || currentThemeId !== previousThemeIdRef.current) {
previousThemeIdRef.current = currentThemeId; previousThemeIdRef.current = currentThemeId;
if (currentThemeId) { // Применяем тему (даже если null - вернемся к дефолтной теме)
changeTheme(currentThemeId); changeTheme(currentThemeId);
} }
}
}; };
socketService.connect(); socketService.connect();
@ -268,14 +271,17 @@ const GamePage = () => {
}; };
const handleUpdateRoomQuestions = async (newQuestions) => { const handleUpdateRoomQuestions = async (newQuestions) => {
if (!gameState.roomId) return; if (!gameState.roomId || !user?.id) return;
try { try {
// Убеждаемся, что newQuestions - это массив
const questionsArray = Array.isArray(newQuestions) ? newQuestions : [];
socketService.emit('updateRoomPack', { socketService.emit('updateRoomPack', {
roomId: gameState.roomId, roomId: gameState.roomId,
roomCode: gameState.roomCode, roomCode: gameState.roomCode,
userId: user.id, userId: user.id,
questions: newQuestions questions: questionsArray
}); });
} catch (error) { } catch (error) {
console.error('Error updating room pack:', error); console.error('Error updating room pack:', error);

View file

@ -19,7 +19,8 @@ const RoomPage = () => {
const { changeTheme } = useTheme(); const { changeTheme } = useTheme();
// Храним предыдущий themeId комнаты для отслеживания изменений // Храним предыдущий themeId комнаты для отслеживания изменений
const previousThemeIdRef = useRef(null); // Используем undefined для обозначения первой загрузки (не установлено)
const previousThemeIdRef = useRef(undefined);
// Callback для автоматической навигации при старте игры // Callback для автоматической навигации при старте игры
const handleGameStartedEvent = useCallback(() => { const handleGameStartedEvent = useCallback(() => {
@ -120,29 +121,29 @@ const RoomPage = () => {
} }
}; };
// Показываем модальное окно выбора роли, если allowSpectators === true и пользователь авторизован // Единая логика присоединения к комнате
useEffect(() => {
if (
room &&
user &&
!joined &&
!isRoleSelectionModalOpen &&
room.allowSpectators &&
!participants.some((p) => p.userId === user.id)
) {
setIsRoleSelectionModalOpen(true);
}
}, [room, user, joined, participants, isRoleSelectionModalOpen]);
// Автоматическое присоединение как PLAYER, если зрители не разрешены
useEffect(() => { useEffect(() => {
const handleJoin = async () => { const handleJoin = async () => {
if (room && user && !joined && !isRoleSelectionModalOpen) { // Пропускаем если нет комнаты, пользователя или уже присоединились
if (!room || !user || joined) return;
// Проверяем, не является ли пользователь уже участником
const isParticipant = participants.some((p) => p.userId === user.id); const isParticipant = participants.some((p) => p.userId === user.id);
if (!isParticipant) { if (isParticipant) {
// Если зрители не разрешены, присоединяемся как PLAYER автоматически setJoined(true);
return;
}
// Если зрители разрешены, показываем модальное окно выбора роли
if (room.allowSpectators) {
if (!isRoleSelectionModalOpen) {
setIsRoleSelectionModalOpen(true);
}
return;
}
// Если зрители не разрешены, автоматически присоединяемся как PLAYER
// Присоединение разрешено независимо от статуса игры (WAITING, PLAYING, FINISHED) // Присоединение разрешено независимо от статуса игры (WAITING, PLAYING, FINISHED)
if (!room.allowSpectators) {
try { try {
setJoinError(null); setJoinError(null);
await joinRoom(room.id, user.id, user.name || 'Гость', 'PLAYER'); await joinRoom(room.id, user.id, user.name || 'Гость', 'PLAYER');
@ -154,11 +155,6 @@ const RoomPage = () => {
setJoinError(errorMessage); setJoinError(errorMessage);
alert(errorMessage); alert(errorMessage);
} }
}
} else {
setJoined(true);
}
}
}; };
handleJoin(); handleJoin();
@ -228,13 +224,14 @@ const RoomPage = () => {
const handleGameStateUpdated = (state) => { const handleGameStateUpdated = (state) => {
const currentThemeId = state.themeId || null; const currentThemeId = state.themeId || null;
// Применяем тему если она изменилась или если это первое присоединение (previousThemeIdRef.current === null) const isFirstLoad = previousThemeIdRef.current === undefined;
if (currentThemeId !== previousThemeIdRef.current) {
// Применяем тему при первой загрузке или если она изменилась
if (isFirstLoad || currentThemeId !== previousThemeIdRef.current) {
previousThemeIdRef.current = currentThemeId; previousThemeIdRef.current = currentThemeId;
if (currentThemeId) { // Применяем тему (даже если null - вернемся к дефолтной теме)
changeTheme(currentThemeId); changeTheme(currentThemeId);
} }
}
}; };
socketService.on('gameStateUpdated', handleGameStateUpdated); socketService.on('gameStateUpdated', handleGameStateUpdated);
@ -248,9 +245,12 @@ const RoomPage = () => {
if (!roomCode || !room) return; if (!roomCode || !room) return;
const currentThemeId = room.themeId || null; const currentThemeId = room.themeId || null;
// Применяем тему если она существует и еще не применена (первое присоединение) или изменилась const isFirstLoad = previousThemeIdRef.current === undefined;
if (currentThemeId && currentThemeId !== previousThemeIdRef.current) {
// Применяем тему при первой загрузке или если она изменилась
if (isFirstLoad || currentThemeId !== previousThemeIdRef.current) {
previousThemeIdRef.current = currentThemeId; previousThemeIdRef.current = currentThemeId;
// Применяем тему (даже если null - вернемся к дефолтной теме)
changeTheme(currentThemeId); changeTheme(currentThemeId);
} }
}, [roomCode, room?.themeId, changeTheme]); }, [roomCode, room?.themeId, changeTheme]);