301 lines
10 KiB
React
301 lines
10 KiB
React
|
|
import { useState } from 'react'
|
|||
|
|
import './GameManagementModal.css'
|
|||
|
|
|
|||
|
|
const GameManagementModal = ({
|
|||
|
|
isOpen,
|
|||
|
|
onClose,
|
|||
|
|
room,
|
|||
|
|
participants,
|
|||
|
|
currentQuestion,
|
|||
|
|
currentQuestionIndex,
|
|||
|
|
totalQuestions,
|
|||
|
|
revealedAnswers,
|
|||
|
|
onStartGame,
|
|||
|
|
onEndGame,
|
|||
|
|
onNextQuestion,
|
|||
|
|
onPreviousQuestion,
|
|||
|
|
onRevealAnswer,
|
|||
|
|
onHideAnswer,
|
|||
|
|
onShowAllAnswers,
|
|||
|
|
onHideAllAnswers,
|
|||
|
|
onAwardPoints,
|
|||
|
|
onPenalty,
|
|||
|
|
}) => {
|
|||
|
|
const [activeTab, setActiveTab] = useState('players') // players | game | answers | scoring
|
|||
|
|
const [selectedPlayer, setSelectedPlayer] = useState(null)
|
|||
|
|
const [customPoints, setCustomPoints] = useState(10)
|
|||
|
|
|
|||
|
|
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 = (index) => {
|
|||
|
|
if (revealedAnswers.includes(index)) {
|
|||
|
|
onHideAnswer(index)
|
|||
|
|
} else {
|
|||
|
|
onRevealAnswer(index)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const handleAwardPoints = (points) => {
|
|||
|
|
if (selectedPlayer) {
|
|||
|
|
onAwardPoints(selectedPlayer, points)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const handlePenalty = () => {
|
|||
|
|
if (selectedPlayer) {
|
|||
|
|
onPenalty(selectedPlayer)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="game-mgmt-modal-backdrop" onClick={handleBackdropClick}>
|
|||
|
|
<div className="game-mgmt-modal-content">
|
|||
|
|
{/* Header with title and close button */}
|
|||
|
|
<div className="game-mgmt-modal-header">
|
|||
|
|
<h2>🎛 Управление игрой</h2>
|
|||
|
|
<button className="game-mgmt-close" onClick={onClose}>×</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Tabs navigation */}
|
|||
|
|
<div className="game-mgmt-tabs">
|
|||
|
|
<button
|
|||
|
|
className={`tab ${activeTab === 'players' ? 'active' : ''}`}
|
|||
|
|
onClick={() => setActiveTab('players')}
|
|||
|
|
>
|
|||
|
|
👥 Игроки
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
className={`tab ${activeTab === 'game' ? 'active' : ''}`}
|
|||
|
|
onClick={() => setActiveTab('game')}
|
|||
|
|
>
|
|||
|
|
🎮 Игра
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
className={`tab ${activeTab === 'answers' ? 'active' : ''}`}
|
|||
|
|
onClick={() => setActiveTab('answers')}
|
|||
|
|
disabled={gameStatus !== 'PLAYING' || !currentQuestion}
|
|||
|
|
>
|
|||
|
|
👁 Ответы
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
className={`tab ${activeTab === 'scoring' ? 'active' : ''}`}
|
|||
|
|
onClick={() => setActiveTab('scoring')}
|
|||
|
|
disabled={gameStatus !== 'PLAYING' || participants.length === 0}
|
|||
|
|
>
|
|||
|
|
➕ Очки
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Tab content */}
|
|||
|
|
<div className="game-mgmt-body">
|
|||
|
|
{/* PLAYERS TAB */}
|
|||
|
|
{activeTab === 'players' && (
|
|||
|
|
<div className="tab-content">
|
|||
|
|
<h3>Участники ({participants.length})</h3>
|
|||
|
|
<div className="players-list">
|
|||
|
|
{participants.length === 0 ? (
|
|||
|
|
<p className="empty-message">Нет участников</p>
|
|||
|
|
) : (
|
|||
|
|
participants.map((participant) => (
|
|||
|
|
<div key={participant.id} className="player-item">
|
|||
|
|
<div className="player-info">
|
|||
|
|
<span className="player-name">{participant.name}</span>
|
|||
|
|
<span className="player-role">
|
|||
|
|
{participant.role === 'HOST' && '👑 Ведущий'}
|
|||
|
|
{participant.role === 'SPECTATOR' && '👀 Зритель'}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="player-score">
|
|||
|
|
{participant.score || 0} очков
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
))
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* GAME CONTROLS TAB */}
|
|||
|
|
{activeTab === 'game' && (
|
|||
|
|
<div className="tab-content">
|
|||
|
|
<h3>Управление игрой</h3>
|
|||
|
|
|
|||
|
|
<div className="game-status">
|
|||
|
|
<strong>Статус:</strong>
|
|||
|
|
{gameStatus === 'WAITING' && ' Ожидание'}
|
|||
|
|
{gameStatus === 'PLAYING' && ' Идет игра'}
|
|||
|
|
{gameStatus === 'FINISHED' && ' Завершена'}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{gameStatus === 'WAITING' && (
|
|||
|
|
<button
|
|||
|
|
className="mgmt-button start-button"
|
|||
|
|
onClick={onStartGame}
|
|||
|
|
disabled={participants.length < 2}
|
|||
|
|
>
|
|||
|
|
▶️ Начать игру
|
|||
|
|
</button>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{gameStatus === 'PLAYING' && (
|
|||
|
|
<div className="game-controls">
|
|||
|
|
<div className="question-nav">
|
|||
|
|
<button
|
|||
|
|
className="mgmt-button"
|
|||
|
|
onClick={onPreviousQuestion}
|
|||
|
|
disabled={currentQuestionIndex === 0}
|
|||
|
|
>
|
|||
|
|
⏮ Предыдущий
|
|||
|
|
</button>
|
|||
|
|
<span className="question-indicator">
|
|||
|
|
Вопрос {currentQuestionIndex + 1} / {totalQuestions}
|
|||
|
|
</span>
|
|||
|
|
<button
|
|||
|
|
className="mgmt-button"
|
|||
|
|
onClick={onNextQuestion}
|
|||
|
|
disabled={currentQuestionIndex >= totalQuestions - 1}
|
|||
|
|
>
|
|||
|
|
Следующий ⏭
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
<button
|
|||
|
|
className="mgmt-button end-button"
|
|||
|
|
onClick={onEndGame}
|
|||
|
|
>
|
|||
|
|
⏹ Завершить игру
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<div className="game-info">
|
|||
|
|
<div className="info-item">
|
|||
|
|
<span>Игроков:</span> <strong>{participants.length}</strong>
|
|||
|
|
</div>
|
|||
|
|
{gameStatus === 'PLAYING' && totalQuestions > 0 && (
|
|||
|
|
<div className="info-item">
|
|||
|
|
<span>Вопросов:</span> <strong>{totalQuestions}</strong>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* ANSWERS CONTROL TAB */}
|
|||
|
|
{activeTab === 'answers' && currentQuestion && (
|
|||
|
|
<div className="tab-content">
|
|||
|
|
<h3>Управление ответами</h3>
|
|||
|
|
|
|||
|
|
<button
|
|||
|
|
className="mgmt-button toggle-all-button"
|
|||
|
|
onClick={areAllAnswersRevealed ? onHideAllAnswers : onShowAllAnswers}
|
|||
|
|
>
|
|||
|
|
{areAllAnswersRevealed ? '🙈 Скрыть все' : '👁 Показать все'}
|
|||
|
|
</button>
|
|||
|
|
|
|||
|
|
<div className="answers-grid">
|
|||
|
|
{currentQuestion.answers.map((answer, index) => (
|
|||
|
|
<button
|
|||
|
|
key={index}
|
|||
|
|
className={`answer-button ${
|
|||
|
|
revealedAnswers.includes(index) ? 'revealed' : 'hidden'
|
|||
|
|
}`}
|
|||
|
|
onClick={() => handleRevealAnswer(index)}
|
|||
|
|
>
|
|||
|
|
<span className="answer-num">{index + 1}</span>
|
|||
|
|
<span className="answer-txt">{answer.text}</span>
|
|||
|
|
<span className="answer-pts">{answer.points}</span>
|
|||
|
|
</button>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* SCORING TAB */}
|
|||
|
|
{activeTab === 'scoring' && (
|
|||
|
|
<div className="tab-content">
|
|||
|
|
<h3>Начисление очков</h3>
|
|||
|
|
|
|||
|
|
<div className="player-selector">
|
|||
|
|
<label>Выберите игрока:</label>
|
|||
|
|
<select
|
|||
|
|
value={selectedPlayer || ''}
|
|||
|
|
onChange={(e) => setSelectedPlayer(e.target.value || null)}
|
|||
|
|
>
|
|||
|
|
<option value="">-- Выберите игрока --</option>
|
|||
|
|
{participants
|
|||
|
|
.filter((p) => p.role === 'PLAYER')
|
|||
|
|
.map((player) => (
|
|||
|
|
<option key={player.id} value={player.id}>
|
|||
|
|
{player.name} ({player.score || 0} очков)
|
|||
|
|
</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{selectedPlayer && (
|
|||
|
|
<div className="scoring-section">
|
|||
|
|
<div className="quick-points">
|
|||
|
|
<button
|
|||
|
|
className="mgmt-button points-button"
|
|||
|
|
onClick={() => handleAwardPoints(5)}
|
|||
|
|
>
|
|||
|
|
+5
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
className="mgmt-button points-button"
|
|||
|
|
onClick={() => handleAwardPoints(10)}
|
|||
|
|
>
|
|||
|
|
+10
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
className="mgmt-button points-button"
|
|||
|
|
onClick={() => handleAwardPoints(20)}
|
|||
|
|
>
|
|||
|
|
+20
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
className="mgmt-button penalty-button"
|
|||
|
|
onClick={handlePenalty}
|
|||
|
|
>
|
|||
|
|
❌ Промах
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="custom-points">
|
|||
|
|
<input
|
|||
|
|
type="number"
|
|||
|
|
min="1"
|
|||
|
|
max="100"
|
|||
|
|
value={customPoints}
|
|||
|
|
onChange={(e) => setCustomPoints(parseInt(e.target.value) || 0)}
|
|||
|
|
/>
|
|||
|
|
<button
|
|||
|
|
className="mgmt-button custom-button"
|
|||
|
|
onClick={() => handleAwardPoints(customPoints)}
|
|||
|
|
>
|
|||
|
|
Начислить {customPoints}
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default GameManagementModal
|
|||
|
|
|