fix errors
This commit is contained in:
+38
-17
@@ -19,7 +19,7 @@ const App = () => {
|
|||||||
const [currentAnswerResult, setCurrentAnswerResult] = useState(null);
|
const [currentAnswerResult, setCurrentAnswerResult] = useState(null);
|
||||||
const [otherPlayersResults, setOtherPlayersResults] = useState([]);
|
const [otherPlayersResults, setOtherPlayersResults] = useState([]);
|
||||||
|
|
||||||
// Новые состояния для лобби
|
//состояния для лобби
|
||||||
const [showLobby, setShowLobby] = useState(true);
|
const [showLobby, setShowLobby] = useState(true);
|
||||||
const [currentPlayer, setCurrentPlayer] = useState(null);
|
const [currentPlayer, setCurrentPlayer] = useState(null);
|
||||||
const [players, setPlayers] = useState([]);
|
const [players, setPlayers] = useState([]);
|
||||||
@@ -381,22 +381,43 @@ const App = () => {
|
|||||||
|
|
||||||
if (quizCompleted) {
|
if (quizCompleted) {
|
||||||
const score = calculateScore();
|
const score = calculateScore();
|
||||||
|
|
||||||
|
// Формируем массив всех игроков для таблицы лидеров
|
||||||
const allPlayersScores = [
|
const allPlayersScores = [
|
||||||
{ ...currentPlayer, isCurrent: true },
|
{
|
||||||
...players.filter(p => p.id !== currentPlayer.id)
|
...currentPlayer,
|
||||||
].sort((a, b) => b.score - a.score);
|
isCurrent: true,
|
||||||
|
totalAnswers: userAnswers.filter(a => a && !a.timeExpired).length,
|
||||||
|
correctAnswers: userAnswers.filter(a => {
|
||||||
|
if (!a || a.timeExpired) return false;
|
||||||
|
if (a.id) {
|
||||||
|
const question = quizData.questions[userAnswers.indexOf(a)];
|
||||||
|
return question.answers.find(ans => ans.id === a.id)?.isCorrect;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).length
|
||||||
|
},
|
||||||
|
...players
|
||||||
|
.filter(p => p.id !== currentPlayer.id)
|
||||||
|
.map(p => ({
|
||||||
|
...p,
|
||||||
|
isCurrent: false,
|
||||||
|
totalAnswers: 0, // В реальном приложении здесь были бы данные
|
||||||
|
correctAnswers: 0
|
||||||
|
}))
|
||||||
|
].sort((a, b) => (b.score || 0) - (a.score || 0));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QuizResults
|
<QuizResults
|
||||||
score={score}
|
score={score}
|
||||||
totalQuestions={quizData.questions.length}
|
totalQuestions={quizData.questions.length}
|
||||||
userAnswers={userAnswers}
|
userAnswers={userAnswers}
|
||||||
quizData={quizData}
|
quizData={quizData}
|
||||||
onRestart={handleRestart}
|
onRestart={handleRestart}
|
||||||
timeExpiredQuestions={timeExpiredQuestions}
|
timeExpiredQuestions={timeExpiredQuestions}
|
||||||
allPlayers={allPlayersScores}
|
allPlayers={allPlayersScores}
|
||||||
currentPlayer={currentPlayer}
|
currentPlayer={currentPlayer}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,11 +487,11 @@ const App = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="navigation-buttons">
|
<div className="navigation-buttons">
|
||||||
{hasPrevious && !isTimeExpired && (
|
{/*hasPrevious && !isTimeExpired && (
|
||||||
<button onClick={handlePrevious} className="nav-button prev">
|
<button onClick={handlePrevious} className="nav-button prev">
|
||||||
← Назад
|
← Назад
|
||||||
</button>
|
</button>
|
||||||
)}
|
)*/}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -130,4 +130,232 @@
|
|||||||
.score-number {
|
.score-number {
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-section {
|
||||||
|
margin: 30px 0;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-section h3 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px 20px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
gap: 15px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-item:hover {
|
||||||
|
transform: translateX(5px);
|
||||||
|
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-item.current-player {
|
||||||
|
border-color: #667eea;
|
||||||
|
background: linear-gradient(90deg, #e8eaf6 0%, #ffffff 100%);
|
||||||
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-item.first {
|
||||||
|
background: linear-gradient(90deg, #fff3cd 0%, #ffffff 100%);
|
||||||
|
border-color: #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-item.second {
|
||||||
|
background: linear-gradient(90deg, #e8e8e8 0%, #ffffff 100%);
|
||||||
|
border-color: #c0c0c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-item.third {
|
||||||
|
background: linear-gradient(90deg, #ffe0b0 0%, #ffffff 100%);
|
||||||
|
border-color: #cd7f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-rank {
|
||||||
|
min-width: 50px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank-number {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-avatar {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-name {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.host-badge {
|
||||||
|
background: #ff9800;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.you-badge {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 4px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-score {
|
||||||
|
text-align: right;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-value {
|
||||||
|
display: block;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-item.first .score-value {
|
||||||
|
color: #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-item.second .score-value {
|
||||||
|
color: #c0c0c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-item.third .score-value {
|
||||||
|
color: #cd7f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-item.current-player .score-value {
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #999;
|
||||||
|
display: block;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Точки за ответ */
|
||||||
|
.points-earned {
|
||||||
|
margin-top: 5px;
|
||||||
|
color: #4caf50;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.points-earned strong {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Адаптивность для таблицы лидеров */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.leaderboard-item {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 12px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-rank {
|
||||||
|
min-width: 35px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-avatar {
|
||||||
|
font-size: 2rem;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-info {
|
||||||
|
width: 100%;
|
||||||
|
order: 3;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-score {
|
||||||
|
margin-left: auto;
|
||||||
|
min-width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-value {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-stats {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Анимация появления */
|
||||||
|
.leaderboard-item {
|
||||||
|
animation: slideInLeft 0.5s ease forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaderboard-item:nth-child(1) { animation-delay: 0.1s; }
|
||||||
|
.leaderboard-item:nth-child(2) { animation-delay: 0.2s; }
|
||||||
|
.leaderboard-item:nth-child(3) { animation-delay: 0.3s; }
|
||||||
|
.leaderboard-item:nth-child(4) { animation-delay: 0.4s; }
|
||||||
|
.leaderboard-item:nth-child(5) { animation-delay: 0.5s; }
|
||||||
|
.leaderboard-item:nth-child(6) { animation-delay: 0.6s; }
|
||||||
|
|
||||||
|
@keyframes slideInLeft {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import './QuizResults.css';
|
import './QuizResults.css';
|
||||||
|
|
||||||
const QuizResults = ({ score, totalQuestions, userAnswers, quizData, onRestart, timeExpiredQuestions }) => {
|
const QuizResults = ({
|
||||||
|
score,
|
||||||
|
totalQuestions,
|
||||||
|
userAnswers,
|
||||||
|
quizData,
|
||||||
|
onRestart,
|
||||||
|
timeExpiredQuestions,
|
||||||
|
allPlayers = [], // Добавляем allPlayers с значением по умолчанию
|
||||||
|
currentPlayer = null
|
||||||
|
}) => {
|
||||||
const percentage = (score / totalQuestions) * 100;
|
const percentage = (score / totalQuestions) * 100;
|
||||||
|
|
||||||
let resultMessage = "";
|
let resultMessage = "";
|
||||||
@@ -42,7 +51,7 @@ const QuizResults = ({ score, totalQuestions, userAnswers, quizData, onRestart,
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getAnswerStatus = (question, answer, index) => {
|
const getAnswerStatus = (question, answer, index) => {
|
||||||
if (timeExpiredQuestions.includes(index)) {
|
if (timeExpiredQuestions?.includes(index)) {
|
||||||
return "expired";
|
return "expired";
|
||||||
}
|
}
|
||||||
if (question.type === "single" && answer && !answer.timeExpired) {
|
if (question.type === "single" && answer && !answer.timeExpired) {
|
||||||
@@ -55,11 +64,28 @@ const QuizResults = ({ score, totalQuestions, userAnswers, quizData, onRestart,
|
|||||||
return "unanswered";
|
return "unanswered";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Функция для получения эмодзи медали
|
||||||
|
const getMedalEmoji = (index) => {
|
||||||
|
if (index === 0) return "🥇";
|
||||||
|
if (index === 1) return "🥈";
|
||||||
|
if (index === 2) return "🥉";
|
||||||
|
return `#${index + 1}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для получения класса места
|
||||||
|
const getRankClass = (index) => {
|
||||||
|
if (index === 0) return "first";
|
||||||
|
if (index === 1) return "second";
|
||||||
|
if (index === 2) return "third";
|
||||||
|
return "other";
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="results-container">
|
<div className="results-container">
|
||||||
<div className="results-card">
|
<div className="results-card">
|
||||||
<h2>Ваши результаты</h2>
|
<h2>🏆 Результаты викторины</h2>
|
||||||
|
|
||||||
|
{/* Основной результат */}
|
||||||
<div className={`score-circle ${resultClass}`}>
|
<div className={`score-circle ${resultClass}`}>
|
||||||
<div className="score-number">{score}</div>
|
<div className="score-number">{score}</div>
|
||||||
<div className="score-total">/{totalQuestions}</div>
|
<div className="score-total">/{totalQuestions}</div>
|
||||||
@@ -68,17 +94,62 @@ const QuizResults = ({ score, totalQuestions, userAnswers, quizData, onRestart,
|
|||||||
<div className="result-message">
|
<div className="result-message">
|
||||||
<p>{resultMessage}</p>
|
<p>{resultMessage}</p>
|
||||||
<p className="percentage">Правильных ответов: {Math.round(percentage)}%</p>
|
<p className="percentage">Правильных ответов: {Math.round(percentage)}%</p>
|
||||||
{timeExpiredQuestions.length > 0 && (
|
{timeExpiredQuestions?.length > 0 && (
|
||||||
<p className="time-warning">
|
<p className="time-warning">
|
||||||
⚠️ На {timeExpiredQuestions.length} вопрос{(timeExpiredQuestions.length > 1 ? 'ах' : 'е')} время истекло
|
⚠️ На {timeExpiredQuestions.length} вопрос{(timeExpiredQuestions.length > 1 ? 'ах' : 'е')} время истекло
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{currentPlayer && (
|
||||||
|
<p className="total-points">
|
||||||
|
🎯 Всего очков: <strong>{currentPlayer.score || 0}</strong>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Таблица лидеров */}
|
||||||
|
{allPlayers && allPlayers.length > 0 && (
|
||||||
|
<div className="leaderboard-section">
|
||||||
|
<h3>👑 Таблица лидеров</h3>
|
||||||
|
<div className="leaderboard">
|
||||||
|
{allPlayers.map((player, index) => (
|
||||||
|
<div
|
||||||
|
key={player.id || index}
|
||||||
|
className={`leaderboard-item ${getRankClass(index)} ${player.isCurrent ? 'current-player' : ''}`}
|
||||||
|
>
|
||||||
|
<div className="leaderboard-rank">
|
||||||
|
<span className="rank-number">{getMedalEmoji(index)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="leaderboard-avatar">{player.avatar || "👤"}</div>
|
||||||
|
<div className="leaderboard-info">
|
||||||
|
<div className="leaderboard-name">
|
||||||
|
{player.name || "Игрок"}
|
||||||
|
{player.isHost && <span className="host-badge">👑 Хост</span>}
|
||||||
|
{player.isCurrent && <span className="you-badge">Вы</span>}
|
||||||
|
</div>
|
||||||
|
<div className="leaderboard-stats">
|
||||||
|
<span className="stat-item">
|
||||||
|
⭐ {player.correctAnswers || 0} правильных
|
||||||
|
</span>
|
||||||
|
<span className="stat-item">
|
||||||
|
📝 {player.totalAnswers || 0} ответов
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="leaderboard-score">
|
||||||
|
<span className="score-value">{player.score || 0}</span>
|
||||||
|
<span className="score-label">очков</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Обзор ответов */}
|
||||||
<div className="answers-review">
|
<div className="answers-review">
|
||||||
<h3>Обзор ответов:</h3>
|
<h3>📋 Обзор ваших ответов:</h3>
|
||||||
{quizData.questions.map((question, index) => {
|
{quizData && quizData.questions && quizData.questions.map((question, index) => {
|
||||||
const userAnswer = userAnswers[index];
|
const userAnswer = userAnswers?.[index];
|
||||||
const status = getAnswerStatus(question, userAnswer, index);
|
const status = getAnswerStatus(question, userAnswer, index);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -94,7 +165,7 @@ const QuizResults = ({ score, totalQuestions, userAnswers, quizData, onRestart,
|
|||||||
<div className="user-answer">
|
<div className="user-answer">
|
||||||
<strong>Ваш ответ:</strong> {getUserAnswerText(question, userAnswer, index)}
|
<strong>Ваш ответ:</strong> {getUserAnswerText(question, userAnswer, index)}
|
||||||
</div>
|
</div>
|
||||||
{status !== "expired" && status !== "pending" && (
|
{status !== "expired" && status !== "pending" && status !== "unanswered" && (
|
||||||
<div className="correct-answer">
|
<div className="correct-answer">
|
||||||
<strong>Правильный ответ:</strong> {getCorrectAnswerText(question, index)}
|
<strong>Правильный ответ:</strong> {getCorrectAnswerText(question, index)}
|
||||||
</div>
|
</div>
|
||||||
@@ -104,25 +175,16 @@ const QuizResults = ({ score, totalQuestions, userAnswers, quizData, onRestart,
|
|||||||
<strong>⏱️ Время на ответ:</strong> {userAnswer.timeSpent} сек
|
<strong>⏱️ Время на ответ:</strong> {userAnswer.timeSpent} сек
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{userAnswer?.pointsEarned > 0 && (
|
||||||
|
<div className="points-earned">
|
||||||
|
<strong>🎯 Очков получено:</strong> +{userAnswer.pointsEarned}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="leaderboard">
|
|
||||||
<h3>🏆 Итоговая таблица лидеров</h3>
|
|
||||||
{allPlayers.map((player, index) => (
|
|
||||||
<div key={player.id} className={`leaderboard-item ${player.isCurrent ? 'current' : ''}`}>
|
|
||||||
<div className="leaderboard-rank">{index + 1}</div>
|
|
||||||
<div className="leaderboard-avatar">{player.avatar}</div>
|
|
||||||
<div className="leaderboard-name">
|
|
||||||
{player.name}
|
|
||||||
{player.isCurrent && <span className="you-badge">(Вы)</span>}
|
|
||||||
</div>
|
|
||||||
<div className="leaderboard-score">{player.score} очков</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button onClick={onRestart} className="restart-button">
|
<button onClick={onRestart} className="restart-button">
|
||||||
Пройти викторину снова 🔄
|
Пройти викторину снова 🔄
|
||||||
|
|||||||
Reference in New Issue
Block a user