sto-k-odnomu/src/components/GameManagementModal.jsx

999 lines
37 KiB
React
Raw Normal View History

2026-01-08 20:14:58 +00:00
import { useState } from 'react'
2026-01-08 20:59:44 +00:00
import { questionsApi } from '../services/api'
2026-01-08 20:14:58 +00:00
import './GameManagementModal.css'
2026-01-08 20:59:44 +00:00
import './QuestionsModal.css'
2026-01-08 20:14:58 +00:00
const GameManagementModal = ({
isOpen,
onClose,
room,
participants,
currentQuestion,
currentQuestionIndex,
totalQuestions,
revealedAnswers,
2026-01-08 20:59:44 +00:00
questions = [],
onUpdateQuestions,
availablePacks = [],
2026-01-08 20:14:58 +00:00
onStartGame,
onEndGame,
onNextQuestion,
onPreviousQuestion,
onRevealAnswer,
onHideAnswer,
onShowAllAnswers,
onHideAllAnswers,
onAwardPoints,
onPenalty,
2026-01-09 16:44:33 +00:00
onUpdatePlayerName,
onUpdatePlayerScore,
onKickPlayer,
2026-01-08 20:14:58 +00:00
}) => {
2026-01-08 20:59:44 +00:00
const [activeTab, setActiveTab] = useState('players') // players | game | answers | scoring | questions
2026-01-08 20:14:58 +00:00
const [selectedPlayer, setSelectedPlayer] = useState(null)
const [customPoints, setCustomPoints] = useState(10)
2026-01-09 16:44:33 +00:00
// Player editing state
const [editingPlayerId, setEditingPlayerId] = useState(null)
const [editingPlayerName, setEditingPlayerName] = useState('')
const [editingPlayerScore, setEditingPlayerScore] = useState('')
const [editMode, setEditMode] = useState(null) // 'name' | 'score'
2026-01-08 20:59:44 +00:00
// 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())
2026-01-08 21:19:49 +00:00
const [searchQuery, setSearchQuery] = useState('')
const [viewingQuestion, setViewingQuestion] = useState(null)
const [showAnswers, setShowAnswers] = useState(false)
2026-01-08 20:14:58 +00:00
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()
}
2026-01-08 21:44:38 +00:00
const handleRevealAnswer = (answerId) => {
if (revealedAnswers.includes(answerId)) {
onHideAnswer(answerId)
2026-01-08 20:14:58 +00:00
} else {
2026-01-08 21:44:38 +00:00
onRevealAnswer(answerId)
2026-01-08 20:14:58 +00:00
}
}
const handleAwardPoints = (points) => {
if (selectedPlayer) {
onAwardPoints(selectedPlayer, points)
}
}
const handlePenalty = () => {
if (selectedPlayer) {
onPenalty(selectedPlayer)
}
}
2026-01-09 16:44:33 +00:00
// 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()
}
}
2026-01-08 20:59:44 +00:00
// 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 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
}
const isValid = jsonContent.every(q =>
q.id &&
typeof q.text === 'string' &&
Array.isArray(q.answers) &&
q.answers.every(a => a.text && typeof a.points === 'number')
)
if (!isValid) {
setJsonError('Неверный формат JSON. Ожидается массив объектов с полями: id, text, answers')
return
}
onUpdateQuestions(jsonContent)
setJsonError('')
alert(`Успешно импортировано ${jsonContent.length} вопросов`)
} catch (error) {
setJsonError('Ошибка при импорте: ' + error.message)
}
}
reader.readAsText(file)
}
input.click()
}
const handleSelectPack = async (packId) => {
if (!packId) {
setPackQuestions([])
setSelectedPack(null)
2026-01-08 21:19:49 +00:00
setSearchQuery('')
setViewingQuestion(null)
setShowAnswers(false)
2026-01-08 20:59:44 +00:00
return
}
try {
const response = await questionsApi.getPack(packId)
setPackQuestions(response.data.questions || [])
setSelectedPack(packId)
setSelectedQuestionIndices(new Set())
2026-01-08 21:19:49 +00:00
setSearchQuery('')
setViewingQuestion(null)
setShowAnswers(false)
2026-01-08 20:59:44 +00:00
} catch (error) {
console.error('Error fetching pack:', error)
setJsonError('Ошибка загрузки пака вопросов')
}
}
2026-01-08 21:19:49 +00:00
// Фильтрация вопросов по поисковому запросу
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)
}
2026-01-08 20:59:44 +00:00
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)
2026-01-08 21:19:49 +00:00
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 })),
2026-01-08 20:59:44 +00:00
}))
const updatedQuestions = [...questions, ...copiedQuestions]
onUpdateQuestions(updatedQuestions)
setSelectedQuestionIndices(new Set())
2026-01-08 21:19:49 +00:00
setSearchQuery('')
2026-01-08 20:59:44 +00:00
setShowPackImport(false)
setJsonError('')
alert(`Импортировано ${copiedQuestions.length} вопросов`)
}
2026-01-08 20:14:58 +00:00
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>
2026-01-08 20:59:44 +00:00
<button
className={`tab ${activeTab === 'questions' ? 'active' : ''}`}
onClick={() => setActiveTab('questions')}
>
Вопросы
</button>
2026-01-08 20:14:58 +00:00
</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">
2026-01-09 16:44:33 +00:00
{editingPlayerId === participant.id && editMode === 'name' ? (
<div className="player-edit-field">
<input
type="text"
value={editingPlayerName}
onChange={(e) => setEditingPlayerName(e.target.value)}
onKeyDown={(e) => handleKeyDown(e, 'name')}
autoFocus
maxLength={50}
className="player-edit-input"
/>
<button className="player-edit-save" onClick={handleSavePlayerName}></button>
<button className="player-edit-cancel" onClick={handleCancelEdit}></button>
</div>
) : (
<span className="player-name">
{participant.name}
<button
className="player-edit-btn"
onClick={() => handleStartEditName(participant)}
title="Редактировать имя"
>
</button>
</span>
)}
2026-01-08 20:14:58 +00:00
<span className="player-role">
{participant.role === 'HOST' && '👑 Ведущий'}
{participant.role === 'SPECTATOR' && '👀 Зритель'}
</span>
</div>
2026-01-09 16:44:33 +00:00
<div className="player-score-section">
{editingPlayerId === participant.id && editMode === 'score' ? (
<div className="player-edit-field">
<input
type="number"
value={editingPlayerScore}
onChange={(e) => setEditingPlayerScore(e.target.value)}
onKeyDown={(e) => handleKeyDown(e, 'score')}
autoFocus
className="player-edit-input player-edit-input-score"
/>
<button className="player-edit-save" onClick={handleSavePlayerScore}></button>
<button className="player-edit-cancel" onClick={handleCancelEdit}></button>
</div>
) : (
<span className="player-score">
{participant.score || 0} очков
<button
className="player-edit-btn"
onClick={() => handleStartEditScore(participant)}
title="Редактировать очки"
>
</button>
</span>
)}
2026-01-08 20:14:58 +00:00
</div>
2026-01-09 16:44:33 +00:00
{participant.role !== 'HOST' && onKickPlayer && (
<button
className="player-kick-btn"
onClick={() => {
if (window.confirm(`Удалить игрока ${participant.name}?`)) {
onKickPlayer(participant.id)
}
}}
title="Удалить игрока"
>
🚫
</button>
)}
2026-01-08 20:14:58 +00:00
</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
2026-01-08 21:44:38 +00:00
key={answer.id || index}
2026-01-08 20:14:58 +00:00
className={`answer-button ${
2026-01-08 21:44:38 +00:00
revealedAnswers.includes(answer.id) ? 'revealed' : 'hidden'
2026-01-08 20:14:58 +00:00
}`}
2026-01-08 21:44:38 +00:00
onClick={() => handleRevealAnswer(answer.id)}
2026-01-08 20:14:58 +00:00
>
<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>
)}
2026-01-08 20:59:44 +00:00
{/* QUESTIONS TAB */}
{activeTab === 'questions' && (
<div className="tab-content questions-tab-content">
<h3>Управление вопросами</h3>
<div className="questions-modal-actions">
<button
className="questions-modal-export-button"
onClick={handleExportJson}
>
📥 Экспорт JSON
</button>
<button
className="questions-modal-import-button"
onClick={handleImportJson}
>
📤 Импорт JSON
</button>
{availablePacks.length > 0 && (
<button
className="questions-modal-pack-import-button"
onClick={() => setShowPackImport(!showPackImport)}
>
📦 {showPackImport ? 'Скрыть импорт' : 'Импорт из пака'}
</button>
)}
</div>
{jsonError && (
<div className="questions-modal-error">{jsonError}</div>
)}
{showPackImport && availablePacks.length > 0 && (
<div className="pack-import-section">
<h4>Импорт вопросов из пака</h4>
<select
value={selectedPack || ''}
onChange={(e) => handleSelectPack(e.target.value)}
className="pack-import-select"
>
<option value="">-- Выберите пак --</option>
{availablePacks.map(pack => (
<option key={pack.id} value={pack.id}>
{pack.name} ({pack.questionCount} вопросов)
</option>
))}
</select>
{packQuestions.length > 0 && (
<div className="pack-questions-list">
2026-01-08 21:19:49 +00:00
{/* Поиск */}
<div className="pack-search-container">
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="🔍 Поиск вопросов..."
className="pack-search-input"
/>
</div>
2026-01-08 20:59:44 +00:00
<div className="pack-questions-header">
2026-01-08 21:19:49 +00:00
<div className="pack-questions-header-left">
<span>Выберите вопросы для импорта:</span>
<div className="pack-select-all-buttons">
{filteredPackQuestions.length > 0 && (
<>
{areAllVisibleSelected() ? (
<button
onClick={handleDeselectAll}
className="pack-select-all-button"
>
Снять выбор
</button>
) : (
<button
onClick={handleSelectAll}
className="pack-select-all-button"
>
Выбрать все ({filteredPackQuestions.length})
</button>
)}
</>
)}
</div>
</div>
2026-01-08 20:59:44 +00:00
<button
onClick={handleImportSelected}
disabled={selectedQuestionIndices.size === 0}
className="pack-import-confirm-button"
>
Импортировать ({selectedQuestionIndices.size})
</button>
</div>
<div className="pack-questions-items">
2026-01-08 21:19:49 +00:00
{filteredPackQuestions.length === 0 ? (
<div className="pack-no-results">
{searchQuery ? 'Вопросы не найдены' : 'Нет вопросов в паке'}
2026-01-08 20:59:44 +00:00
</div>
2026-01-08 21:19:49 +00:00
) : (
filteredPackQuestions.map((q, filteredIdx) => {
const originalIndex = packQuestions.findIndex(pq => pq === q)
return (
<div key={originalIndex} className="pack-question-item">
<input
type="checkbox"
checked={selectedQuestionIndices.has(originalIndex)}
onChange={() => handleToggleQuestion(originalIndex)}
/>
<div className="pack-question-content">
<strong>{q.text || q.question}</strong>
<span className="pack-question-info">
{q.answers?.length || 0} ответов
</span>
</div>
<button
onClick={() => handleViewQuestion(q)}
className="pack-view-question-button"
title="Просмотр вопроса"
>
👁
</button>
</div>
)
})
)}
</div>
</div>
)}
{/* Модальное окно просмотра вопроса */}
{viewingQuestion && (
<div className="pack-question-viewer-backdrop" onClick={handleCloseViewer}>
<div className="pack-question-viewer" onClick={(e) => e.stopPropagation()}>
<div className="pack-question-viewer-header">
<h4>Просмотр вопроса</h4>
<button
className="pack-question-viewer-close"
onClick={handleCloseViewer}
>
×
</button>
</div>
<div className="pack-question-viewer-content">
<div className="pack-question-viewer-text">
{viewingQuestion.text || viewingQuestion.question}
</div>
<button
className="pack-show-answers-button"
onClick={() => setShowAnswers(!showAnswers)}
>
{showAnswers ? '🙈 Скрыть ответы' : '👁 Показать ответы'}
</button>
{showAnswers && (
<div className="pack-question-answers">
{viewingQuestion.answers?.map((answer, idx) => (
<div key={idx} className="pack-answer-item">
<span className="pack-answer-text">{answer.text}</span>
<span className="pack-answer-points">{answer.points} очков</span>
</div>
))}
</div>
)}
</div>
2026-01-08 20:59:44 +00:00
</div>
</div>
)}
</div>
)}
<div className="questions-modal-form">
<input
type="text"
value={questionText}
onChange={(e) => setQuestionText(e.target.value)}
placeholder="Введите текст вопроса"
className="questions-modal-input"
/>
<div className="questions-modal-answers">
<div className="questions-modal-answers-header">
<span>Ответы:</span>
<button
className="questions-modal-add-answer-button"
onClick={handleAddAnswer}
type="button"
>
+ Добавить ответ
</button>
</div>
{answers.map((answer, index) => (
<div key={index} className="questions-modal-answer-row">
<input
type="text"
value={answer.text}
onChange={(e) => handleAnswerChange(index, 'text', e.target.value)}
placeholder={`Ответ ${index + 1}`}
className="questions-modal-answer-input"
/>
<input
type="number"
value={answer.points}
onChange={(e) => handleAnswerChange(index, 'points', e.target.value)}
className="questions-modal-points-input"
min="0"
/>
{answers.length > 1 && (
<button
className="questions-modal-remove-answer-button"
onClick={() => handleRemoveAnswer(index)}
type="button"
>
×
</button>
)}
</div>
))}
</div>
<div className="questions-modal-form-buttons">
<button
className="questions-modal-save-button"
onClick={handleSaveQuestion}
>
{editingQuestion ? 'Сохранить изменения' : 'Добавить вопрос'}
</button>
{editingQuestion && (
<button
className="questions-modal-cancel-button"
onClick={handleCancelEditQuestion}
>
Отмена
</button>
)}
</div>
</div>
<div className="questions-modal-list">
<h4 className="questions-modal-list-title">
Вопросы ({questions.length})
</h4>
{questions.length === 0 ? (
<p className="questions-modal-empty">Нет вопросов. Добавьте вопросы для игры.</p>
) : (
<div className="questions-modal-items">
{questions.map((question) => (
<div key={question.id} className="questions-modal-item">
<div className="questions-modal-item-content">
<div className="questions-modal-item-text">{question.text}</div>
<div className="questions-modal-item-info">
{question.answers.length} ответов
</div>
</div>
<div className="questions-modal-item-actions">
<button
className="questions-modal-edit-button"
onClick={() => handleEditQuestion(question)}
title="Редактировать"
>
</button>
<button
className="questions-modal-delete-button"
onClick={() => handleDeleteQuestion(question.id)}
title="Удалить"
>
×
</button>
</div>
</div>
))}
</div>
)}
</div>
</div>
)}
2026-01-08 20:14:58 +00:00
</div>
</div>
</div>
)
}
export default GameManagementModal