mirror of
https://github.com/Stijnvandenbroek/stamp.git
synced 2026-01-14 23:16:54 +01:00
chore: organise frontend
This commit is contained in:
@@ -2,41 +2,25 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const Home = ({ setSessionId, setQuizStarted }) => {
|
const Home = ({ setSessionId, setQuizStarted }) => {
|
||||||
const [files, setFiles] = useState([]);
|
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
|
||||||
const [fileNames, setFileNames] = useState('');
|
|
||||||
const [settingsOpen, setSettingsOpen] = useState(true);
|
// File state
|
||||||
const [setting1, setSetting1] = useState(false);
|
const [files, setFiles] = useState([]);
|
||||||
const [setting2, setSetting2] = useState(false);
|
const [fileNames, setFileNames] = useState('');
|
||||||
const [isFileListVisible, setIsFileListVisible] = useState(false);
|
const [isFileListVisible, setIsFileListVisible] = useState(false);
|
||||||
|
|
||||||
|
// Settings state
|
||||||
|
const [settingsOpen, setSettingsOpen] = useState(true);
|
||||||
|
const [setting1, setSetting1] = useState(false);
|
||||||
|
const [setting2, setSetting2] = useState(false);
|
||||||
const [quizSettings, setQuizSettings] = useState({
|
const [quizSettings, setQuizSettings] = useState({
|
||||||
repeat_on_mistake: false,
|
repeat_on_mistake: false,
|
||||||
shuffle_answers: false,
|
shuffle_answers: false,
|
||||||
randomise_order: false,
|
randomise_order: false,
|
||||||
question_count_multiplier: 1,
|
question_count_multiplier: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Backend API URL - use environment variable or default
|
|
||||||
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
|
|
||||||
|
|
||||||
// Add useEffect to handle file list visibility
|
|
||||||
useEffect(() => {
|
|
||||||
if (fileNames) {
|
|
||||||
// Slight delay to ensure smooth transition
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
setIsFileListVisible(true);
|
|
||||||
}, 50);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
} else {
|
|
||||||
setIsFileListVisible(false);
|
|
||||||
}
|
|
||||||
}, [fileNames]);
|
|
||||||
|
|
||||||
const handleFileChange = (e) => {
|
|
||||||
const selectedFiles = Array.from(e.target.files);
|
|
||||||
setFiles(selectedFiles);
|
|
||||||
setFileNames(selectedFiles.map(file => file.name).join(', '));
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// API functions
|
||||||
const handleFileUpload = async () => {
|
const handleFileUpload = async () => {
|
||||||
if (files.length === 0) return alert("Please select files");
|
if (files.length === 0) return alert("Please select files");
|
||||||
|
|
||||||
@@ -68,6 +52,13 @@ const Home = ({ setSessionId, setQuizStarted }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Event handlers
|
||||||
|
const handleFileChange = (e) => {
|
||||||
|
const selectedFiles = Array.from(e.target.files);
|
||||||
|
setFiles(selectedFiles);
|
||||||
|
setFileNames(selectedFiles.map(file => file.name).join(', '));
|
||||||
|
};
|
||||||
|
|
||||||
const toggleSettings = () => {
|
const toggleSettings = () => {
|
||||||
setSettingsOpen(!settingsOpen);
|
setSettingsOpen(!settingsOpen);
|
||||||
};
|
};
|
||||||
@@ -84,7 +75,27 @@ const Home = ({ setSessionId, setQuizStarted }) => {
|
|||||||
setQuizSettings({ ...quizSettings, shuffle_answers: newSetting });
|
setQuizSettings({ ...quizSettings, shuffle_answers: newSetting });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Main container style with flexbox to position elements side by side
|
const handleRandomizeOrderToggle = () => {
|
||||||
|
setQuizSettings({ ...quizSettings, randomise_order: !quizSettings.randomise_order });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleQuestionCountChange = (e) => {
|
||||||
|
setQuizSettings({ ...quizSettings, question_count_multiplier: parseInt(e.target.value) });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Effects
|
||||||
|
useEffect(() => {
|
||||||
|
if (fileNames) {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setIsFileListVisible(true);
|
||||||
|
}, 50);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
} else {
|
||||||
|
setIsFileListVisible(false);
|
||||||
|
}
|
||||||
|
}, [fileNames]);
|
||||||
|
|
||||||
|
// Styles
|
||||||
const containerStyle = {
|
const containerStyle = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
@@ -97,16 +108,14 @@ const Home = ({ setSessionId, setQuizStarted }) => {
|
|||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Left side container for quiz setup
|
|
||||||
const leftContainerStyle = {
|
const leftContainerStyle = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginRight: '30px', // Space between left and right containers
|
marginRight: '30px',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Right side container for file names with improved fade transition
|
|
||||||
const rightContainerStyle = {
|
const rightContainerStyle = {
|
||||||
backgroundColor: '#333',
|
backgroundColor: '#333',
|
||||||
padding: '20px',
|
padding: '20px',
|
||||||
@@ -123,31 +132,6 @@ const Home = ({ setSessionId, setQuizStarted }) => {
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileNameHeaderStyle = {
|
|
||||||
fontWeight: 'bold',
|
|
||||||
marginBottom: '10px',
|
|
||||||
position: 'sticky',
|
|
||||||
top: '0',
|
|
||||||
backgroundColor: '#333',
|
|
||||||
zIndex: 1,
|
|
||||||
paddingBottom: '10px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const fileNamesScrollContainerStyle = {
|
|
||||||
overflowY: 'auto',
|
|
||||||
flexGrow: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
const fileNamesContainerStyle = {
|
|
||||||
backgroundColor: '#444',
|
|
||||||
borderRadius: '5px',
|
|
||||||
padding: '10px',
|
|
||||||
marginTop: '10px',
|
|
||||||
fontSize: '0.9em',
|
|
||||||
color: 'white',
|
|
||||||
wordBreak: 'break-all',
|
|
||||||
};
|
|
||||||
|
|
||||||
const uploadContainerStyle = {
|
const uploadContainerStyle = {
|
||||||
backgroundColor: '#333',
|
backgroundColor: '#333',
|
||||||
padding: '20px',
|
padding: '20px',
|
||||||
@@ -265,6 +249,31 @@ const Home = ({ setSessionId, setQuizStarted }) => {
|
|||||||
marginLeft: '5px',
|
marginLeft: '5px',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fileNameHeaderStyle = {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginBottom: '10px',
|
||||||
|
position: 'sticky',
|
||||||
|
top: '0',
|
||||||
|
backgroundColor: '#333',
|
||||||
|
zIndex: 1,
|
||||||
|
paddingBottom: '10px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileNamesScrollContainerStyle = {
|
||||||
|
overflowY: 'auto',
|
||||||
|
flexGrow: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileNamesContainerStyle = {
|
||||||
|
backgroundColor: '#444',
|
||||||
|
borderRadius: '5px',
|
||||||
|
padding: '10px',
|
||||||
|
marginTop: '10px',
|
||||||
|
fontSize: '0.9em',
|
||||||
|
color: 'white',
|
||||||
|
wordBreak: 'break-all',
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={containerStyle}>
|
<div style={containerStyle}>
|
||||||
<div style={leftContainerStyle}>
|
<div style={leftContainerStyle}>
|
||||||
@@ -284,7 +293,6 @@ const Home = ({ setSessionId, setQuizStarted }) => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Show/Hide Settings Button */}
|
|
||||||
<div
|
<div
|
||||||
onClick={toggleSettings}
|
onClick={toggleSettings}
|
||||||
style={{
|
style={{
|
||||||
@@ -297,7 +305,6 @@ const Home = ({ setSessionId, setQuizStarted }) => {
|
|||||||
<span>{settingsOpen ? 'Hide Settings' : 'Show Settings'}</span>
|
<span>{settingsOpen ? 'Hide Settings' : 'Show Settings'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Settings Section */}
|
|
||||||
<div style={settingsContainerStyle}>
|
<div style={settingsContainerStyle}>
|
||||||
<div style={toggleStyle}>
|
<div style={toggleStyle}>
|
||||||
<span style={settingTextStyle}>Repeat on Mistake</span>
|
<span style={settingTextStyle}>Repeat on Mistake</span>
|
||||||
@@ -314,7 +321,7 @@ const Home = ({ setSessionId, setQuizStarted }) => {
|
|||||||
<div style={toggleStyle}>
|
<div style={toggleStyle}>
|
||||||
<span style={settingTextStyle}>Randomize Order</span>
|
<span style={settingTextStyle}>Randomize Order</span>
|
||||||
<div
|
<div
|
||||||
onClick={() => setQuizSettings({ ...quizSettings, randomise_order: !quizSettings.randomise_order })}
|
onClick={handleRandomizeOrderToggle}
|
||||||
style={toggleSwitchStyle(quizSettings.randomise_order)}
|
style={toggleSwitchStyle(quizSettings.randomise_order)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -327,16 +334,13 @@ const Home = ({ setSessionId, setQuizStarted }) => {
|
|||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={quizSettings.question_count_multiplier}
|
value={quizSettings.question_count_multiplier}
|
||||||
onChange={(e) =>
|
onChange={handleQuestionCountChange}
|
||||||
setQuizSettings({ ...quizSettings, question_count_multiplier: parseInt(e.target.value) })
|
|
||||||
}
|
|
||||||
style={{ ...inputStyle, ...hideSpinnerStyle }}
|
style={{ ...inputStyle, ...hideSpinnerStyle }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right side container for selected files with improved fade transition */}
|
|
||||||
{fileNames && (
|
{fileNames && (
|
||||||
<div style={rightContainerStyle}>
|
<div style={rightContainerStyle}>
|
||||||
<div style={fileNameHeaderStyle}>Selected Files:</div>
|
<div style={fileNameHeaderStyle}>Selected Files:</div>
|
||||||
|
|||||||
@@ -3,20 +3,184 @@ import axios from 'axios';
|
|||||||
|
|
||||||
const Quiz = ({ sessionId, onGoHome, onRetry }) => {
|
const Quiz = ({ sessionId, onGoHome, onRetry }) => {
|
||||||
const API_URL = process.env.REACT_APP_API_URL;
|
const API_URL = process.env.REACT_APP_API_URL;
|
||||||
|
|
||||||
|
// Quiz state
|
||||||
const [questionData, setQuestionData] = useState(null);
|
const [questionData, setQuestionData] = useState(null);
|
||||||
const [selectedAnswers, setSelectedAnswers] = useState([]);
|
const [quizCompleted, setQuizCompleted] = useState(false);
|
||||||
const [userAnswer, setUserAnswer] = useState('');
|
|
||||||
const [totalQuestions, setTotalQuestions] = useState(0);
|
const [totalQuestions, setTotalQuestions] = useState(0);
|
||||||
const [correctAnswerCount, setCorrectAnswersCount] = useState(0);
|
const [correctAnswerCount, setCorrectAnswersCount] = useState(0);
|
||||||
const [incorrectAnswersCount, setIncorrectAnswersCount] = useState(0);
|
const [incorrectAnswersCount, setIncorrectAnswersCount] = useState(0);
|
||||||
|
|
||||||
|
// Answer state
|
||||||
|
const [selectedAnswers, setSelectedAnswers] = useState([]);
|
||||||
|
const [userAnswer, setUserAnswer] = useState('');
|
||||||
const [correctAnswers, setCorrectAnswers] = useState('');
|
const [correctAnswers, setCorrectAnswers] = useState('');
|
||||||
const [quizCompleted, setQuizCompleted] = useState(false);
|
|
||||||
const [timer, setTimer] = useState(0);
|
// UI state
|
||||||
const [timerId, setTimerId] = useState(null);
|
|
||||||
const [feedback, setFeedback] = useState('');
|
const [feedback, setFeedback] = useState('');
|
||||||
const [isFeedbackVisible, setIsFeedbackVisible] = useState(false);
|
const [isFeedbackVisible, setIsFeedbackVisible] = useState(false);
|
||||||
const [fadeOut, setFadeOut] = useState(false);
|
const [fadeOut, setFadeOut] = useState(false);
|
||||||
const [showResults, setShowResults] = useState(false);
|
const [showResults, setShowResults] = useState(false);
|
||||||
|
|
||||||
|
// Timer state
|
||||||
|
const [timer, setTimer] = useState(0);
|
||||||
|
const [timerId, setTimerId] = useState(null);
|
||||||
|
|
||||||
|
// API functions
|
||||||
|
const fetchQuestion = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${API_URL}/next-question/?session_id=${sessionId}`);
|
||||||
|
console.log("Fetched Question Data:", response.data);
|
||||||
|
|
||||||
|
if (response.data.message === 'Quiz complete!') {
|
||||||
|
setFadeOut(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setQuizCompleted(true);
|
||||||
|
setQuestionData(null);
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowResults(true);
|
||||||
|
}, 100);
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
setQuestionData(response.data);
|
||||||
|
setSelectedAnswers([]);
|
||||||
|
setUserAnswer('');
|
||||||
|
setFeedback('');
|
||||||
|
setIsFeedbackVisible(false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('Error fetching question: ' + (error.response?.data?.error || error.message));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchQuizStats = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${API_URL}/quiz-stats/?session_id=${sessionId}`);
|
||||||
|
setTotalQuestions(response.data.total_questions);
|
||||||
|
setCorrectAnswersCount(response.data.correct_answers);
|
||||||
|
setIncorrectAnswersCount(response.data.incorrect_answers);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching quiz stats:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
let submissionData;
|
||||||
|
if (questionData.options.length > 1) {
|
||||||
|
submissionData = {
|
||||||
|
session_id: sessionId,
|
||||||
|
selected_answers: selectedAnswers,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
submissionData = {
|
||||||
|
session_id: sessionId,
|
||||||
|
selected_answers: [userAnswer.trim()],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(`${API_URL}/submit-answer/`, submissionData);
|
||||||
|
const result = response.data.result;
|
||||||
|
setFeedback(result);
|
||||||
|
setIsFeedbackVisible(true);
|
||||||
|
|
||||||
|
if (result === 'Correct') {
|
||||||
|
fetchQuestion();
|
||||||
|
} else {
|
||||||
|
setCorrectAnswers(response.data.correct_answers.join(', '));
|
||||||
|
await moveQuestionToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchQuizStats();
|
||||||
|
} catch (error) {
|
||||||
|
alert('Error submitting answer: ' + (error.response?.data?.error || error.message));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const moveQuestionToBottom = async () => {
|
||||||
|
try {
|
||||||
|
const questionIndex = questionData.question_index;
|
||||||
|
await axios.post(`${API_URL}/move-question-to-bottom/`, {
|
||||||
|
session_id: sessionId,
|
||||||
|
question_index: questionIndex,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
alert('Error moving question to bottom: ' + (error.response?.data?.error || error.message));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Event handlers
|
||||||
|
const handleMultipleAnswerChange = (answerText) => {
|
||||||
|
setSelectedAnswers((prev) => {
|
||||||
|
if (prev.includes(answerText)) {
|
||||||
|
return prev.filter((answer) => answer !== answerText);
|
||||||
|
}
|
||||||
|
return [...prev, answerText];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAnswerChange = (answerText) => {
|
||||||
|
setSelectedAnswers([answerText]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUserAnswerChange = (event) => {
|
||||||
|
setUserAnswer(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContinueClick = () => {
|
||||||
|
setIsFeedbackVisible(false);
|
||||||
|
setFeedback('');
|
||||||
|
fetchQuestion();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyPress = (event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
if (isFeedbackVisible) {
|
||||||
|
handleContinueClick();
|
||||||
|
} else {
|
||||||
|
const canSubmit = questionData.options.length > 1
|
||||||
|
? selectedAnswers.length > 0
|
||||||
|
: userAnswer.trim() !== '';
|
||||||
|
|
||||||
|
if (canSubmit) {
|
||||||
|
handleSubmit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
const formatTime = (totalSeconds) => {
|
||||||
|
const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0');
|
||||||
|
const minutes = String(Math.floor((totalSeconds % 3600) / 60)).padStart(2, '0');
|
||||||
|
const seconds = String(totalSeconds % 60).padStart(2, '0');
|
||||||
|
return `${hours}:${minutes}:${seconds}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Effects
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('keypress', handleKeyPress);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keypress', handleKeyPress);
|
||||||
|
};
|
||||||
|
}, [isFeedbackVisible, selectedAnswers, userAnswer, questionData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchQuestion();
|
||||||
|
fetchQuizStats();
|
||||||
|
const id = setInterval(() => setTimer((prev) => prev + 1), 1000);
|
||||||
|
setTimerId(id);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(id);
|
||||||
|
};
|
||||||
|
}, [sessionId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (quizCompleted) {
|
||||||
|
clearInterval(timerId);
|
||||||
|
}
|
||||||
|
}, [quizCompleted, timerId]);
|
||||||
|
|
||||||
// Styles
|
// Styles
|
||||||
const pageStyle = {
|
const pageStyle = {
|
||||||
@@ -118,160 +282,7 @@ const Quiz = ({ sessionId, onGoHome, onRetry }) => {
|
|||||||
transition: 'opacity 0.5s ease-out, transform 0.5s ease-out',
|
transition: 'opacity 0.5s ease-out, transform 0.5s ease-out',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Keyboard event handler
|
// Render quiz completion screen
|
||||||
const handleKeyPress = (event) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
if (isFeedbackVisible) {
|
|
||||||
handleContinueClick();
|
|
||||||
} else {
|
|
||||||
const canSubmit = questionData.options.length > 1
|
|
||||||
? selectedAnswers.length > 0
|
|
||||||
: userAnswer.trim() !== '';
|
|
||||||
|
|
||||||
if (canSubmit) {
|
|
||||||
handleSubmit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Event listeners
|
|
||||||
useEffect(() => {
|
|
||||||
document.addEventListener('keypress', handleKeyPress);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('keypress', handleKeyPress);
|
|
||||||
};
|
|
||||||
}, [isFeedbackVisible, selectedAnswers, userAnswer, questionData]);
|
|
||||||
|
|
||||||
const fetchQuestion = async () => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${API_URL}/next-question/?session_id=${sessionId}`);
|
|
||||||
console.log("Fetched Question Data:", response.data);
|
|
||||||
|
|
||||||
if (response.data.message === 'Quiz complete!') {
|
|
||||||
setFadeOut(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setQuizCompleted(true);
|
|
||||||
setQuestionData(null);
|
|
||||||
setTimeout(() => {
|
|
||||||
setShowResults(true);
|
|
||||||
}, 100);
|
|
||||||
}, 500);
|
|
||||||
} else {
|
|
||||||
setQuestionData(response.data);
|
|
||||||
setSelectedAnswers([]);
|
|
||||||
setUserAnswer('');
|
|
||||||
setFeedback('');
|
|
||||||
setIsFeedbackVisible(false);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
alert('Error fetching question: ' + (error.response?.data?.error || error.message));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchQuizStats = async () => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${API_URL}/quiz-stats/?session_id=${sessionId}`);
|
|
||||||
setTotalQuestions(response.data.total_questions);
|
|
||||||
setCorrectAnswersCount(response.data.correct_answers);
|
|
||||||
setIncorrectAnswersCount(response.data.incorrect_answers);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching quiz stats:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMultipleAnswerChange = (answerText) => {
|
|
||||||
setSelectedAnswers((prev) => {
|
|
||||||
if (prev.includes(answerText)) {
|
|
||||||
return prev.filter((answer) => answer !== answerText);
|
|
||||||
}
|
|
||||||
return [...prev, answerText];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAnswerChange = (answerText) => {
|
|
||||||
setSelectedAnswers([answerText]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUserAnswerChange = (event) => {
|
|
||||||
setUserAnswer(event.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleContinueClick = () => {
|
|
||||||
setIsFeedbackVisible(false);
|
|
||||||
setFeedback('');
|
|
||||||
fetchQuestion();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
let submissionData;
|
|
||||||
if (questionData.options.length > 1) {
|
|
||||||
submissionData = {
|
|
||||||
session_id: sessionId,
|
|
||||||
selected_answers: selectedAnswers,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
submissionData = {
|
|
||||||
session_id: sessionId,
|
|
||||||
selected_answers: [userAnswer.trim()],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await axios.post(`${API_URL}/submit-answer/`, submissionData);
|
|
||||||
const result = response.data.result;
|
|
||||||
setFeedback(result);
|
|
||||||
setIsFeedbackVisible(true);
|
|
||||||
|
|
||||||
if (result === 'Correct') {
|
|
||||||
fetchQuestion();
|
|
||||||
} else {
|
|
||||||
setCorrectAnswers(response.data.correct_answers.join(', '));
|
|
||||||
await moveQuestionToBottom();
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchQuizStats();
|
|
||||||
} catch (error) {
|
|
||||||
alert('Error submitting answer: ' + (error.response?.data?.error || error.message));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const moveQuestionToBottom = async () => {
|
|
||||||
try {
|
|
||||||
const questionIndex = questionData.question_index;
|
|
||||||
await axios.post(`${API_URL}/move-question-to-bottom/`, {
|
|
||||||
session_id: sessionId,
|
|
||||||
question_index: questionIndex,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
alert('Error moving question to bottom: ' + (error.response?.data?.error || error.message));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchQuestion();
|
|
||||||
fetchQuizStats();
|
|
||||||
const id = setInterval(() => setTimer((prev) => prev + 1), 1000);
|
|
||||||
setTimerId(id);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearInterval(id);
|
|
||||||
};
|
|
||||||
}, [sessionId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (quizCompleted) {
|
|
||||||
clearInterval(timerId);
|
|
||||||
}
|
|
||||||
}, [quizCompleted, timerId]);
|
|
||||||
|
|
||||||
const formatTime = (totalSeconds) => {
|
|
||||||
const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0');
|
|
||||||
const minutes = String(Math.floor((totalSeconds % 3600) / 60)).padStart(2, '0');
|
|
||||||
const seconds = String(totalSeconds % 60).padStart(2, '0');
|
|
||||||
return `${hours}:${minutes}:${seconds}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (quizCompleted) {
|
if (quizCompleted) {
|
||||||
const totalAnswered = correctAnswerCount + (totalQuestions - correctAnswerCount);
|
const totalAnswered = correctAnswerCount + (totalQuestions - correctAnswerCount);
|
||||||
const percentageCorrect = totalAnswered > 0 ? (correctAnswerCount / (correctAnswerCount + incorrectAnswersCount)) * 100 : 0;
|
const percentageCorrect = totalAnswered > 0 ? (correctAnswerCount / (correctAnswerCount + incorrectAnswersCount)) * 100 : 0;
|
||||||
@@ -306,8 +317,10 @@ const Quiz = ({ sessionId, onGoHome, onRetry }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loading state
|
||||||
if (!questionData) return <div style={{ color: 'white', textAlign: 'center', paddingTop: '50px' }}>Loading...</div>;
|
if (!questionData) return <div style={{ color: 'white', textAlign: 'center', paddingTop: '50px' }}>Loading...</div>;
|
||||||
|
|
||||||
|
// Quiz question rendering
|
||||||
const hasMultipleOptions = questionData.options.length > 1;
|
const hasMultipleOptions = questionData.options.length > 1;
|
||||||
const hasMultipleCorrectAnswers = questionData.multiple_choice;
|
const hasMultipleCorrectAnswers = questionData.multiple_choice;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user