import { useState } from 'react' import { questionsApi } from '../services/api' import './GameManagementModal.css' import './QuestionsModal.css' const GameManagementModal = ({ isOpen, onClose, room, participants, currentQuestion, currentQuestionIndex, totalQuestions, revealedAnswers, questions = [], onUpdateQuestions, availablePacks = [], onStartGame, onEndGame, onNextQuestion, onPreviousQuestion, onRevealAnswer, onHideAnswer, onShowAllAnswers, onHideAllAnswers, onAwardPoints, onPenalty, onUpdatePlayerName, onUpdatePlayerScore, onKickPlayer, }) => { const [activeTab, setActiveTab] = useState('players') // players | game | answers | scoring | questions const [selectedPlayer, setSelectedPlayer] = useState(null) const [customPoints, setCustomPoints] = useState(10) // Player editing state const [editingPlayerId, setEditingPlayerId] = useState(null) const [editingPlayerName, setEditingPlayerName] = useState('') const [editingPlayerScore, setEditingPlayerScore] = useState('') const [editMode, setEditMode] = useState(null) // 'name' | 'score' // Questions management state const [editingQuestion, setEditingQuestion] = useState(null) const [questionText, setQuestionText] = useState('') const [answers, setAnswers] = useState([ { text: '', points: 100 }, { text: '', points: 80 }, { text: '', points: 60 }, { text: '', points: 40 }, { text: '', points: 20 }, { text: '', points: 10 }, ]) const [jsonError, setJsonError] = useState('') const [showPackImport, setShowPackImport] = useState(false) const [selectedPack, setSelectedPack] = useState(null) const [packQuestions, setPackQuestions] = useState([]) const [selectedQuestionIndices, setSelectedQuestionIndices] = useState(new Set()) const [searchQuery, setSearchQuery] = useState('') const [viewingQuestion, setViewingQuestion] = useState(null) const [showAnswers, setShowAnswers] = useState(false) if (!isOpen) return null const gameStatus = room?.status || 'WAITING' const areAllAnswersRevealed = currentQuestion ? revealedAnswers.length === currentQuestion.answers.length : false // Handlers const handleBackdropClick = (e) => { if (e.target === e.currentTarget) onClose() } const handleRevealAnswer = (answerId) => { if (revealedAnswers.includes(answerId)) { onHideAnswer(answerId) } else { onRevealAnswer(answerId) } } const handleAwardPoints = (points) => { if (selectedPlayer) { onAwardPoints(selectedPlayer, points) } } const handlePenalty = () => { if (selectedPlayer) { onPenalty(selectedPlayer) } } // Player editing handlers const handleStartEditName = (participant) => { setEditingPlayerId(participant.id) setEditingPlayerName(participant.name) setEditMode('name') } const handleStartEditScore = (participant) => { setEditingPlayerId(participant.id) setEditingPlayerScore(String(participant.score || 0)) setEditMode('score') } const handleCancelEdit = () => { setEditingPlayerId(null) setEditingPlayerName('') setEditingPlayerScore('') setEditMode(null) } const handleSavePlayerName = () => { if (editingPlayerName.trim() && onUpdatePlayerName) { onUpdatePlayerName(editingPlayerId, editingPlayerName.trim()) } handleCancelEdit() } const handleSavePlayerScore = () => { const score = parseInt(editingPlayerScore, 10) if (!isNaN(score) && onUpdatePlayerScore) { onUpdatePlayerScore(editingPlayerId, score) } handleCancelEdit() } const handleKeyDown = (e, type) => { if (e.key === 'Enter') { if (type === 'name') { handleSavePlayerName() } else if (type === 'score') { handleSavePlayerScore() } } else if (e.key === 'Escape') { handleCancelEdit() } } // Questions management handlers const resetQuestionForm = () => { setEditingQuestion(null) setQuestionText('') setAnswers([ { text: '', points: 100 }, { text: '', points: 80 }, { text: '', points: 60 }, { text: '', points: 40 }, { text: '', points: 20 }, { text: '', points: 10 }, ]) setJsonError('') } const handleEditQuestion = (question) => { setEditingQuestion(question) setQuestionText(question.text) setAnswers([...question.answers]) setJsonError('') } const handleCancelEditQuestion = () => { resetQuestionForm() } const handleAnswerChange = (index, field, value) => { const updatedAnswers = [...answers] if (field === 'text') { updatedAnswers[index].text = value } else if (field === 'points') { updatedAnswers[index].points = parseInt(value) || 0 } setAnswers(updatedAnswers) } const handleAddAnswer = () => { const minPoints = Math.min(...answers.map(a => a.points)) setAnswers([...answers, { text: '', points: Math.max(0, minPoints - 10) }]) } const handleRemoveAnswer = (index) => { if (answers.length > 1) { setAnswers(answers.filter((_, i) => i !== index)) } } const validateQuestionForm = () => { if (!questionText.trim()) { setJsonError('Введите текст вопроса') return false } if (answers.length === 0) { setJsonError('Добавьте хотя бы один ответ') return false } const hasEmptyAnswers = answers.some(a => !a.text.trim()) if (hasEmptyAnswers) { setJsonError('Заполните все ответы') return false } return true } const handleSaveQuestion = () => { if (!validateQuestionForm()) return const questionData = { id: editingQuestion ? editingQuestion.id : Date.now(), text: questionText.trim(), answers: answers .filter(a => a.text.trim()) .map(a => ({ text: a.text.trim(), points: a.points, })), } let updatedQuestions if (editingQuestion) { updatedQuestions = questions.map(q => q.id === editingQuestion.id ? questionData : q ) } else { updatedQuestions = [...questions, questionData] } onUpdateQuestions(updatedQuestions) resetQuestionForm() } const handleDeleteQuestion = (questionId) => { if (window.confirm('Вы уверены, что хотите удалить этот вопрос?')) { const updatedQuestions = questions.filter(q => q.id !== questionId) onUpdateQuestions(updatedQuestions) if (editingQuestion && editingQuestion.id === questionId) { resetQuestionForm() } } } const handleExportJson = () => { try { const jsonString = JSON.stringify(questions, null, 2) const blob = new Blob([jsonString], { type: 'application/json' }) const url = URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = 'questions.json' document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) setJsonError('') } catch (error) { setJsonError('Ошибка при экспорте: ' + error.message) } } const handleDownloadTemplate = () => { const template = [ { text: 'Назовите самый популярный вид спорта в мире', answers: [ { text: 'Футбол', points: 100 }, { text: 'Баскетбол', points: 80 }, { text: 'Теннис', points: 60 }, { text: 'Хоккей', points: 40 }, { text: 'Волейбол', points: 20 }, { text: 'Бокс', points: 10 }, ] }, { text: 'Что люди обычно берут с собой на пляж?', answers: [ { text: 'Полотенце', points: 100 }, { text: 'Крем от солнца', points: 80 }, { text: 'Очки', points: 60 }, { text: 'Зонт', points: 40 }, { text: 'Книга', points: 20 }, { text: 'Еда', points: 10 }, ] } ] try { const jsonString = JSON.stringify(template, null, 2) const blob = new Blob([jsonString], { type: 'application/json' }) const url = URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = 'template_questions.json' document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) setJsonError('') } catch (error) { setJsonError('Ошибка при скачивании шаблона: ' + error.message) } } const handleImportJson = () => { const input = document.createElement('input') input.type = 'file' input.accept = '.json' input.onchange = (e) => { const file = e.target.files[0] if (!file) return const reader = new FileReader() reader.onload = (event) => { try { const jsonContent = JSON.parse(event.target.result) if (!Array.isArray(jsonContent)) { setJsonError('JSON должен содержать массив вопросов') return } // Валидация - id опционален (будет сгенерирован автоматически) const isValid = jsonContent.every(q => typeof q.text === 'string' && Array.isArray(q.answers) && q.answers.every(a => a.text && typeof a.points === 'number') ) if (!isValid) { setJsonError('Неверный формат JSON. Ожидается массив объектов с полями: text, answers') return } // Добавляем id если его нет const questionsWithIds = jsonContent.map((q, idx) => ({ ...q, id: q.id || Date.now() + Math.random() + idx, answers: q.answers.map((a, aidx) => ({ ...a, id: a.id || `answer-${Date.now()}-${idx}-${aidx}` })) })) onUpdateQuestions(questionsWithIds) setJsonError('') alert(`Успешно импортировано ${questionsWithIds.length} вопросов`) } catch (error) { setJsonError('Ошибка при импорте: ' + error.message) } } reader.readAsText(file) } input.click() } const handleSelectPack = async (packId) => { if (!packId) { setPackQuestions([]) setSelectedPack(null) setSearchQuery('') setViewingQuestion(null) setShowAnswers(false) return } try { const response = await questionsApi.getPack(packId) setPackQuestions(response.data.questions || []) setSelectedPack(packId) setSelectedQuestionIndices(new Set()) setSearchQuery('') setViewingQuestion(null) setShowAnswers(false) } catch (error) { console.error('Error fetching pack:', error) setJsonError('Ошибка загрузки пака вопросов') } } // Фильтрация вопросов по поисковому запросу const filteredPackQuestions = packQuestions.filter((q) => { if (!searchQuery.trim()) return true const questionText = (q.text || q.question || '').toLowerCase() return questionText.includes(searchQuery.toLowerCase()) }) // Выбор всех видимых вопросов const handleSelectAll = () => { const allVisibleIndices = new Set( filteredPackQuestions.map((q) => { const originalIndex = packQuestions.findIndex(pq => pq === q) return originalIndex }).filter(idx => idx !== -1) ) const newSelected = new Set(selectedQuestionIndices) allVisibleIndices.forEach(idx => newSelected.add(idx)) setSelectedQuestionIndices(newSelected) } // Снятие выбора со всех видимых вопросов const handleDeselectAll = () => { const visibleIndices = new Set( filteredPackQuestions.map((q) => { const originalIndex = packQuestions.findIndex(pq => pq === q) return originalIndex }).filter(idx => idx !== -1) ) const newSelected = new Set(selectedQuestionIndices) visibleIndices.forEach(idx => newSelected.delete(idx)) setSelectedQuestionIndices(newSelected) } // Проверка, выбраны ли все видимые вопросы const areAllVisibleSelected = () => { if (filteredPackQuestions.length === 0) return false const visibleIndices = filteredPackQuestions.map((q) => { const originalIndex = packQuestions.findIndex(pq => pq === q) return originalIndex }).filter(idx => idx !== -1) return visibleIndices.every(idx => selectedQuestionIndices.has(idx)) } // Просмотр вопроса const handleViewQuestion = (question) => { setViewingQuestion(question) setShowAnswers(false) } // Закрытие просмотра вопроса const handleCloseViewer = () => { setViewingQuestion(null) setShowAnswers(false) } const handleToggleQuestion = (index) => { const newSelected = new Set(selectedQuestionIndices) if (newSelected.has(index)) { newSelected.delete(index) } else { newSelected.add(index) } setSelectedQuestionIndices(newSelected) } const handleImportSelected = () => { const indices = Array.from(selectedQuestionIndices) const questionsToImport = indices.map(idx => packQuestions[idx]).filter(Boolean) const copiedQuestions = questionsToImport.map((q, idx) => ({ id: Date.now() + Math.random() + idx, // Generate new ID text: q.text || q.question || '', answers: (q.answers || []).map(a => ({ text: a.text, points: a.points })), })) const updatedQuestions = [...questions, ...copiedQuestions] onUpdateQuestions(updatedQuestions) setSelectedQuestionIndices(new Set()) setSearchQuery('') setShowPackImport(false) setJsonError('') alert(`Импортировано ${copiedQuestions.length} вопросов`) } return (
{/* Header with title and close button */}

🎛 Управление игрой

{/* Tabs navigation */}
{/* Tab content */}
{/* PLAYERS TAB */} {activeTab === 'players' && (

Участники ({participants.length})

{participants.length === 0 ? (

Нет участников

) : ( participants.map((participant) => (
{editingPlayerId === participant.id && editMode === 'name' ? (
setEditingPlayerName(e.target.value)} onKeyDown={(e) => handleKeyDown(e, 'name')} autoFocus maxLength={50} className="player-edit-input" />
) : ( {participant.name} )} {participant.role === 'HOST' && '👑 Ведущий'} {participant.role === 'SPECTATOR' && '👀 Зритель'}
{editingPlayerId === participant.id && editMode === 'score' ? (
setEditingPlayerScore(e.target.value)} onKeyDown={(e) => handleKeyDown(e, 'score')} autoFocus className="player-edit-input player-edit-input-score" />
) : ( {participant.score || 0} очков )}
{participant.role !== 'HOST' && onKickPlayer && ( )}
)) )}
)} {/* GAME CONTROLS TAB */} {activeTab === 'game' && (

Управление игрой

Статус: {gameStatus === 'WAITING' && ' Ожидание'} {gameStatus === 'PLAYING' && ' Идет игра'} {gameStatus === 'FINISHED' && ' Завершена'}
{gameStatus === 'WAITING' && ( )} {gameStatus === 'PLAYING' && (
Вопрос {currentQuestionIndex + 1} / {totalQuestions}
)}
Игроков: {participants.length}
{gameStatus === 'PLAYING' && totalQuestions > 0 && (
Вопросов: {totalQuestions}
)}
)} {/* ANSWERS CONTROL TAB */} {activeTab === 'answers' && currentQuestion && (

Управление ответами

{currentQuestion.answers.map((answer, index) => ( ))}
)} {/* SCORING TAB */} {activeTab === 'scoring' && (

Начисление очков

{selectedPlayer && (
setCustomPoints(parseInt(e.target.value) || 0)} />
)}
)} {/* QUESTIONS TAB */} {activeTab === 'questions' && (

Управление вопросами

{availablePacks.length > 0 && ( )}
{jsonError && (
{jsonError}
)} {showPackImport && availablePacks.length > 0 && (

Импорт вопросов из пака

{packQuestions.length > 0 && (
{/* Поиск */}
setSearchQuery(e.target.value)} placeholder="🔍 Поиск вопросов..." className="pack-search-input" />
Выберите вопросы для импорта:
{filteredPackQuestions.length > 0 && ( <> {areAllVisibleSelected() ? ( ) : ( )} )}
{filteredPackQuestions.length === 0 ? (
{searchQuery ? 'Вопросы не найдены' : 'Нет вопросов в паке'}
) : ( filteredPackQuestions.map((q, filteredIdx) => { const originalIndex = packQuestions.findIndex(pq => pq === q) return (
handleToggleQuestion(originalIndex)} />
{q.text || q.question} {q.answers?.length || 0} ответов
) }) )}
)} {/* Модальное окно просмотра вопроса */} {viewingQuestion && (
e.stopPropagation()}>

Просмотр вопроса

{viewingQuestion.text || viewingQuestion.question}
{showAnswers && (
{viewingQuestion.answers?.map((answer, idx) => (
{answer.text} {answer.points} очков
))}
)}
)}
)}
setQuestionText(e.target.value)} placeholder="Введите текст вопроса" className="questions-modal-input" />
Ответы:
{answers.map((answer, index) => (
handleAnswerChange(index, 'text', e.target.value)} placeholder={`Ответ ${index + 1}`} className="questions-modal-answer-input" /> handleAnswerChange(index, 'points', e.target.value)} className="questions-modal-points-input" min="0" /> {answers.length > 1 && ( )}
))}
{editingQuestion && ( )}

Вопросы ({questions.length})

{questions.length === 0 ? (

Нет вопросов. Добавьте вопросы для игры.

) : (
{questions.map((question) => (
{question.text}
{question.answers.length} ответов
))}
)}
)}
) } export default GameManagementModal