diff --git a/src/components/GameManagementModal.jsx b/src/components/GameManagementModal.jsx index 2029225..26a7a91 100644 --- a/src/components/GameManagementModal.jsx +++ b/src/components/GameManagementModal.jsx @@ -46,6 +46,9 @@ const GameManagementModal = ({ 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 @@ -244,6 +247,9 @@ const GameManagementModal = ({ if (!packId) { setPackQuestions([]) setSelectedPack(null) + setSearchQuery('') + setViewingQuestion(null) + setShowAnswers(false) return } @@ -252,12 +258,70 @@ const GameManagementModal = ({ 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)) { @@ -272,16 +336,17 @@ const GameManagementModal = ({ const indices = Array.from(selectedQuestionIndices) const questionsToImport = indices.map(idx => packQuestions[idx]).filter(Boolean) - const copiedQuestions = questionsToImport.map(q => ({ - id: Date.now() + Math.random(), - text: q.text, - answers: q.answers.map(a => ({ text: a.text, points: a.points })), + 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} вопросов`) @@ -577,8 +642,42 @@ const GameManagementModal = ({ {packQuestions.length > 0 && (
+ {/* Поиск */} +
+ setSearchQuery(e.target.value)} + placeholder="🔍 Поиск вопросов..." + className="pack-search-input" + /> +
+
- Выберите вопросы для импорта: +
+ Выберите вопросы для импорта: +
+ {filteredPackQuestions.length > 0 && ( + <> + {areAllVisibleSelected() ? ( + + ) : ( + + )} + + )} +
+
+
+ ) + }) + )} +
+ + )} + + {/* Модальное окно просмотра вопроса */} + {viewingQuestion && ( +
+
e.stopPropagation()}> +
+

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

+ +
+
+
+ {viewingQuestion.text || viewingQuestion.question} +
+ + {showAnswers && ( +
+ {viewingQuestion.answers?.map((answer, idx) => ( +
+ {answer.text} + {answer.points} очков +
+ ))} +
+ )} +
)} diff --git a/src/components/QuestionsModal.css b/src/components/QuestionsModal.css index 683982a..5a1b076 100644 --- a/src/components/QuestionsModal.css +++ b/src/components/QuestionsModal.css @@ -582,6 +582,234 @@ 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) { .pack-import-section { padding: 15px; @@ -593,8 +821,37 @@ gap: 10px; } + .pack-questions-header-left { + width: 100%; + } + .pack-import-confirm-button { 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; + } } diff --git a/src/components/QuestionsModal.jsx b/src/components/QuestionsModal.jsx index 537b9bb..277f657 100644 --- a/src/components/QuestionsModal.jsx +++ b/src/components/QuestionsModal.jsx @@ -27,6 +27,9 @@ const QuestionsModal = ({ const [packQuestions, setPackQuestions] = useState([]) const [selectedQuestionIndices, setSelectedQuestionIndices] = useState(new Set()) const [savingToRoom, setSavingToRoom] = useState(false) + const [searchQuery, setSearchQuery] = useState('') + const [viewingQuestion, setViewingQuestion] = useState(null) + const [showAnswers, setShowAnswers] = useState(false) if (!isOpen) return null @@ -207,6 +210,9 @@ const QuestionsModal = ({ if (!packId) { setPackQuestions([]) setSelectedPack(null) + setSearchQuery('') + setViewingQuestion(null) + setShowAnswers(false) return } @@ -215,12 +221,70 @@ const QuestionsModal = ({ 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)) { @@ -236,10 +300,10 @@ const QuestionsModal = ({ const questionsToImport = indices.map(idx => packQuestions[idx]).filter(Boolean) // Create deep copies - const copiedQuestions = questionsToImport.map(q => ({ - id: Date.now() + Math.random(), // Generate new ID - text: q.text, - answers: q.answers.map(a => ({ text: a.text, points: a.points })), + 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] @@ -247,6 +311,7 @@ const QuestionsModal = ({ // Reset setSelectedQuestionIndices(new Set()) + setSearchQuery('') setShowPackImport(false) setJsonError('') alert(`Импортировано ${copiedQuestions.length} вопросов`) @@ -307,8 +372,42 @@ const QuestionsModal = ({ {packQuestions.length > 0 && (
+ {/* Поиск */} +
+ setSearchQuery(e.target.value)} + placeholder="🔍 Поиск вопросов..." + className="pack-search-input" + /> +
+
- Выберите вопросы для импорта: +
+ Выберите вопросы для импорта: +
+ {filteredPackQuestions.length > 0 && ( + <> + {areAllVisibleSelected() ? ( + + ) : ( + + )} + + )} +
+
+
+ ) + }) + )} +
+ + )} + + {/* Модальное окно просмотра вопроса */} + {viewingQuestion && ( +
+
e.stopPropagation()}> +
+

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

+ +
+
+
+ {viewingQuestion.text || viewingQuestion.question} +
+ + {showAnswers && ( +
+ {viewingQuestion.answers?.map((answer, idx) => ( +
+ {answer.text} + {answer.points} очков +
+ ))} +
+ )} +
)}