menu
This commit is contained in:
parent
a02a0d5d1d
commit
0c86868e0f
3 changed files with 601 additions and 38 deletions
|
|
@ -46,6 +46,9 @@ const GameManagementModal = ({
|
||||||
const [selectedPack, setSelectedPack] = useState(null)
|
const [selectedPack, setSelectedPack] = useState(null)
|
||||||
const [packQuestions, setPackQuestions] = useState([])
|
const [packQuestions, setPackQuestions] = useState([])
|
||||||
const [selectedQuestionIndices, setSelectedQuestionIndices] = useState(new Set())
|
const [selectedQuestionIndices, setSelectedQuestionIndices] = useState(new Set())
|
||||||
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
|
const [viewingQuestion, setViewingQuestion] = useState(null)
|
||||||
|
const [showAnswers, setShowAnswers] = useState(false)
|
||||||
|
|
||||||
if (!isOpen) return null
|
if (!isOpen) return null
|
||||||
|
|
||||||
|
|
@ -244,6 +247,9 @@ const GameManagementModal = ({
|
||||||
if (!packId) {
|
if (!packId) {
|
||||||
setPackQuestions([])
|
setPackQuestions([])
|
||||||
setSelectedPack(null)
|
setSelectedPack(null)
|
||||||
|
setSearchQuery('')
|
||||||
|
setViewingQuestion(null)
|
||||||
|
setShowAnswers(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -252,12 +258,70 @@ const GameManagementModal = ({
|
||||||
setPackQuestions(response.data.questions || [])
|
setPackQuestions(response.data.questions || [])
|
||||||
setSelectedPack(packId)
|
setSelectedPack(packId)
|
||||||
setSelectedQuestionIndices(new Set())
|
setSelectedQuestionIndices(new Set())
|
||||||
|
setSearchQuery('')
|
||||||
|
setViewingQuestion(null)
|
||||||
|
setShowAnswers(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching pack:', error)
|
console.error('Error fetching pack:', error)
|
||||||
setJsonError('Ошибка загрузки пака вопросов')
|
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 handleToggleQuestion = (index) => {
|
||||||
const newSelected = new Set(selectedQuestionIndices)
|
const newSelected = new Set(selectedQuestionIndices)
|
||||||
if (newSelected.has(index)) {
|
if (newSelected.has(index)) {
|
||||||
|
|
@ -272,16 +336,17 @@ const GameManagementModal = ({
|
||||||
const indices = Array.from(selectedQuestionIndices)
|
const indices = Array.from(selectedQuestionIndices)
|
||||||
const questionsToImport = indices.map(idx => packQuestions[idx]).filter(Boolean)
|
const questionsToImport = indices.map(idx => packQuestions[idx]).filter(Boolean)
|
||||||
|
|
||||||
const copiedQuestions = questionsToImport.map(q => ({
|
const copiedQuestions = questionsToImport.map((q, idx) => ({
|
||||||
id: Date.now() + Math.random(),
|
id: Date.now() + Math.random() + idx, // Generate new ID
|
||||||
text: q.text,
|
text: q.text || q.question || '',
|
||||||
answers: q.answers.map(a => ({ text: a.text, points: a.points })),
|
answers: (q.answers || []).map(a => ({ text: a.text, points: a.points })),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const updatedQuestions = [...questions, ...copiedQuestions]
|
const updatedQuestions = [...questions, ...copiedQuestions]
|
||||||
onUpdateQuestions(updatedQuestions)
|
onUpdateQuestions(updatedQuestions)
|
||||||
|
|
||||||
setSelectedQuestionIndices(new Set())
|
setSelectedQuestionIndices(new Set())
|
||||||
|
setSearchQuery('')
|
||||||
setShowPackImport(false)
|
setShowPackImport(false)
|
||||||
setJsonError('')
|
setJsonError('')
|
||||||
alert(`Импортировано ${copiedQuestions.length} вопросов`)
|
alert(`Импортировано ${copiedQuestions.length} вопросов`)
|
||||||
|
|
@ -577,8 +642,42 @@ const GameManagementModal = ({
|
||||||
|
|
||||||
{packQuestions.length > 0 && (
|
{packQuestions.length > 0 && (
|
||||||
<div className="pack-questions-list">
|
<div className="pack-questions-list">
|
||||||
|
{/* Поиск */}
|
||||||
|
<div className="pack-search-container">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
placeholder="🔍 Поиск вопросов..."
|
||||||
|
className="pack-search-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="pack-questions-header">
|
<div className="pack-questions-header">
|
||||||
|
<div className="pack-questions-header-left">
|
||||||
<span>Выберите вопросы для импорта:</span>
|
<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>
|
||||||
<button
|
<button
|
||||||
onClick={handleImportSelected}
|
onClick={handleImportSelected}
|
||||||
disabled={selectedQuestionIndices.size === 0}
|
disabled={selectedQuestionIndices.size === 0}
|
||||||
|
|
@ -589,22 +688,76 @@ const GameManagementModal = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pack-questions-items">
|
<div className="pack-questions-items">
|
||||||
{packQuestions.map((q, idx) => (
|
{filteredPackQuestions.length === 0 ? (
|
||||||
<div key={idx} className="pack-question-item">
|
<div className="pack-no-results">
|
||||||
|
{searchQuery ? 'Вопросы не найдены' : 'Нет вопросов в паке'}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
filteredPackQuestions.map((q, filteredIdx) => {
|
||||||
|
const originalIndex = packQuestions.findIndex(pq => pq === q)
|
||||||
|
return (
|
||||||
|
<div key={originalIndex} className="pack-question-item">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={selectedQuestionIndices.has(idx)}
|
checked={selectedQuestionIndices.has(originalIndex)}
|
||||||
onChange={() => handleToggleQuestion(idx)}
|
onChange={() => handleToggleQuestion(originalIndex)}
|
||||||
/>
|
/>
|
||||||
<div className="pack-question-content">
|
<div className="pack-question-content">
|
||||||
<strong>{q.text}</strong>
|
<strong>{q.text || q.question}</strong>
|
||||||
<span className="pack-question-info">
|
<span className="pack-question-info">
|
||||||
{q.answers.length} ответов
|
{q.answers?.length || 0} ответов
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -582,6 +582,234 @@
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Search */
|
||||||
|
.pack-search-container {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-search-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 15px;
|
||||||
|
border: 2px solid rgba(255, 215, 0, 0.3);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1rem;
|
||||||
|
outline: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-search-input::placeholder {
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-search-input:focus {
|
||||||
|
border-color: #ffd700;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
box-shadow: 0 0 10px rgba(255, 215, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header improvements */
|
||||||
|
.pack-questions-header-left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-select-all-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-select-all-button {
|
||||||
|
padding: 8px 15px;
|
||||||
|
background: rgba(78, 205, 196, 0.3);
|
||||||
|
color: #4ecdc4;
|
||||||
|
border: 2px solid rgba(78, 205, 196, 0.5);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-select-all-button:hover {
|
||||||
|
background: rgba(78, 205, 196, 0.5);
|
||||||
|
border-color: #4ecdc4;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* View question button */
|
||||||
|
.pack-view-question-button {
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: rgba(102, 126, 234, 0.3);
|
||||||
|
color: #667eea;
|
||||||
|
border: 2px solid rgba(102, 126, 234, 0.5);
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-view-question-button:hover {
|
||||||
|
background: rgba(102, 126, 234, 0.5);
|
||||||
|
border-color: #667eea;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No results */
|
||||||
|
.pack-no-results {
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
padding: 40px 20px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Question viewer modal */
|
||||||
|
.pack-question-viewer-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 2000;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-question-viewer {
|
||||||
|
background: rgba(20, 20, 30, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 25px;
|
||||||
|
max-width: 600px;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
border: 2px solid rgba(255, 215, 0, 0.3);
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-question-viewer-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
border-bottom: 2px solid rgba(255, 215, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-question-viewer-header h4 {
|
||||||
|
color: #ffd700;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin: 0;
|
||||||
|
text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-question-viewer-close {
|
||||||
|
background: rgba(255, 107, 107, 0.2);
|
||||||
|
color: #ff6b6b;
|
||||||
|
border: 2px solid rgba(255, 107, 107, 0.5);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-question-viewer-close:hover {
|
||||||
|
background: rgba(255, 107, 107, 0.4);
|
||||||
|
border-color: #ff6b6b;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-question-viewer-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-question-viewer-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding: 15px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 2px solid rgba(255, 215, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-show-answers-button {
|
||||||
|
padding: 12px 20px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-show-answers-button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-question-answers {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-answer-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 2px solid rgba(255, 215, 0, 0.2);
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-answer-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-color: rgba(255, 215, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-answer-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1rem;
|
||||||
|
flex: 1;
|
||||||
|
word-wrap: break-word;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-answer-points {
|
||||||
|
color: #ffd700;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-shadow: 0 0 5px rgba(255, 215, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.pack-import-section {
|
.pack-import-section {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
|
@ -593,8 +821,37 @@
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pack-questions-header-left {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.pack-import-confirm-button {
|
.pack-import-confirm-button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pack-question-item {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-view-question-button {
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-question-viewer {
|
||||||
|
padding: 20px;
|
||||||
|
max-height: 90vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-answer-item {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pack-answer-points {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@ const QuestionsModal = ({
|
||||||
const [packQuestions, setPackQuestions] = useState([])
|
const [packQuestions, setPackQuestions] = useState([])
|
||||||
const [selectedQuestionIndices, setSelectedQuestionIndices] = useState(new Set())
|
const [selectedQuestionIndices, setSelectedQuestionIndices] = useState(new Set())
|
||||||
const [savingToRoom, setSavingToRoom] = useState(false)
|
const [savingToRoom, setSavingToRoom] = useState(false)
|
||||||
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
|
const [viewingQuestion, setViewingQuestion] = useState(null)
|
||||||
|
const [showAnswers, setShowAnswers] = useState(false)
|
||||||
|
|
||||||
if (!isOpen) return null
|
if (!isOpen) return null
|
||||||
|
|
||||||
|
|
@ -207,6 +210,9 @@ const QuestionsModal = ({
|
||||||
if (!packId) {
|
if (!packId) {
|
||||||
setPackQuestions([])
|
setPackQuestions([])
|
||||||
setSelectedPack(null)
|
setSelectedPack(null)
|
||||||
|
setSearchQuery('')
|
||||||
|
setViewingQuestion(null)
|
||||||
|
setShowAnswers(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,12 +221,70 @@ const QuestionsModal = ({
|
||||||
setPackQuestions(response.data.questions || [])
|
setPackQuestions(response.data.questions || [])
|
||||||
setSelectedPack(packId)
|
setSelectedPack(packId)
|
||||||
setSelectedQuestionIndices(new Set())
|
setSelectedQuestionIndices(new Set())
|
||||||
|
setSearchQuery('')
|
||||||
|
setViewingQuestion(null)
|
||||||
|
setShowAnswers(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching pack:', error)
|
console.error('Error fetching pack:', error)
|
||||||
setJsonError('Ошибка загрузки пака вопросов')
|
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 handleToggleQuestion = (index) => {
|
||||||
const newSelected = new Set(selectedQuestionIndices)
|
const newSelected = new Set(selectedQuestionIndices)
|
||||||
if (newSelected.has(index)) {
|
if (newSelected.has(index)) {
|
||||||
|
|
@ -236,10 +300,10 @@ const QuestionsModal = ({
|
||||||
const questionsToImport = indices.map(idx => packQuestions[idx]).filter(Boolean)
|
const questionsToImport = indices.map(idx => packQuestions[idx]).filter(Boolean)
|
||||||
|
|
||||||
// Create deep copies
|
// Create deep copies
|
||||||
const copiedQuestions = questionsToImport.map(q => ({
|
const copiedQuestions = questionsToImport.map((q, idx) => ({
|
||||||
id: Date.now() + Math.random(), // Generate new ID
|
id: Date.now() + Math.random() + idx, // Generate new ID
|
||||||
text: q.text,
|
text: q.text || q.question || '',
|
||||||
answers: q.answers.map(a => ({ text: a.text, points: a.points })),
|
answers: (q.answers || []).map(a => ({ text: a.text, points: a.points })),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const updatedQuestions = [...questions, ...copiedQuestions]
|
const updatedQuestions = [...questions, ...copiedQuestions]
|
||||||
|
|
@ -247,6 +311,7 @@ const QuestionsModal = ({
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
setSelectedQuestionIndices(new Set())
|
setSelectedQuestionIndices(new Set())
|
||||||
|
setSearchQuery('')
|
||||||
setShowPackImport(false)
|
setShowPackImport(false)
|
||||||
setJsonError('')
|
setJsonError('')
|
||||||
alert(`Импортировано ${copiedQuestions.length} вопросов`)
|
alert(`Импортировано ${copiedQuestions.length} вопросов`)
|
||||||
|
|
@ -307,8 +372,42 @@ const QuestionsModal = ({
|
||||||
|
|
||||||
{packQuestions.length > 0 && (
|
{packQuestions.length > 0 && (
|
||||||
<div className="pack-questions-list">
|
<div className="pack-questions-list">
|
||||||
|
{/* Поиск */}
|
||||||
|
<div className="pack-search-container">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
placeholder="🔍 Поиск вопросов..."
|
||||||
|
className="pack-search-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="pack-questions-header">
|
<div className="pack-questions-header">
|
||||||
|
<div className="pack-questions-header-left">
|
||||||
<span>Выберите вопросы для импорта:</span>
|
<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>
|
||||||
<button
|
<button
|
||||||
onClick={handleImportSelected}
|
onClick={handleImportSelected}
|
||||||
disabled={selectedQuestionIndices.size === 0}
|
disabled={selectedQuestionIndices.size === 0}
|
||||||
|
|
@ -319,22 +418,76 @@ const QuestionsModal = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pack-questions-items">
|
<div className="pack-questions-items">
|
||||||
{packQuestions.map((q, idx) => (
|
{filteredPackQuestions.length === 0 ? (
|
||||||
<div key={idx} className="pack-question-item">
|
<div className="pack-no-results">
|
||||||
|
{searchQuery ? 'Вопросы не найдены' : 'Нет вопросов в паке'}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
filteredPackQuestions.map((q, filteredIdx) => {
|
||||||
|
const originalIndex = packQuestions.findIndex(pq => pq === q)
|
||||||
|
return (
|
||||||
|
<div key={originalIndex} className="pack-question-item">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={selectedQuestionIndices.has(idx)}
|
checked={selectedQuestionIndices.has(originalIndex)}
|
||||||
onChange={() => handleToggleQuestion(idx)}
|
onChange={() => handleToggleQuestion(originalIndex)}
|
||||||
/>
|
/>
|
||||||
<div className="pack-question-content">
|
<div className="pack-question-content">
|
||||||
<strong>{q.text}</strong>
|
<strong>{q.text || q.question}</strong>
|
||||||
<span className="pack-question-info">
|
<span className="pack-question-info">
|
||||||
{q.answers.length} ответов
|
{q.answers?.length || 0} ответов
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue