From 470bf07608eda03cfaf422cf62df995f4665da00 Mon Sep 17 00:00:00 2001 From: Stijnvandenbroek <70574420+Stijnvandenbroek@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:39:53 +0200 Subject: [PATCH] chore: organise frontend --- frontend/src/components/Home.js | 130 +++++++------ frontend/src/components/Quiz.js | 331 +++++++++++++++++--------------- 2 files changed, 239 insertions(+), 222 deletions(-) diff --git a/frontend/src/components/Home.js b/frontend/src/components/Home.js index c0239cc..0ffb86c 100644 --- a/frontend/src/components/Home.js +++ b/frontend/src/components/Home.js @@ -2,41 +2,25 @@ import React, { useState, useEffect } from 'react'; import axios from 'axios'; const Home = ({ setSessionId, setQuizStarted }) => { - const [files, setFiles] = useState([]); - const [fileNames, setFileNames] = useState(''); - const [settingsOpen, setSettingsOpen] = useState(true); - const [setting1, setSetting1] = useState(false); - const [setting2, setSetting2] = useState(false); + const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000'; + + // File state + const [files, setFiles] = useState([]); + const [fileNames, setFileNames] = useState(''); 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({ repeat_on_mistake: false, shuffle_answers: false, randomise_order: false, 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 () => { 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 = () => { setSettingsOpen(!settingsOpen); }; @@ -84,7 +75,27 @@ const Home = ({ setSessionId, setQuizStarted }) => { 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 = { display: 'flex', justifyContent: 'center', @@ -97,16 +108,14 @@ const Home = ({ setSessionId, setQuizStarted }) => { boxSizing: 'border-box', }; - // Left side container for quiz setup const leftContainerStyle = { display: 'flex', flexDirection: 'column', alignItems: '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 = { backgroundColor: '#333', padding: '20px', @@ -123,31 +132,6 @@ const Home = ({ setSessionId, setQuizStarted }) => { 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 = { backgroundColor: '#333', padding: '20px', @@ -265,6 +249,31 @@ const Home = ({ setSessionId, setQuizStarted }) => { 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 (
@@ -284,7 +293,6 @@ const Home = ({ setSessionId, setQuizStarted }) => {
- {/* Show/Hide Settings Button */}
{ {settingsOpen ? 'Hide Settings' : 'Show Settings'}
- {/* Settings Section */}
Repeat on Mistake @@ -314,7 +321,7 @@ const Home = ({ setSessionId, setQuizStarted }) => {
Randomize Order
setQuizSettings({ ...quizSettings, randomise_order: !quizSettings.randomise_order })} + onClick={handleRandomizeOrderToggle} style={toggleSwitchStyle(quizSettings.randomise_order)} >
{ - setQuizSettings({ ...quizSettings, question_count_multiplier: parseInt(e.target.value) }) - } + onChange={handleQuestionCountChange} style={{ ...inputStyle, ...hideSpinnerStyle }} />
- {/* Right side container for selected files with improved fade transition */} {fileNames && (
Selected Files:
diff --git a/frontend/src/components/Quiz.js b/frontend/src/components/Quiz.js index 831f98c..4b875e2 100644 --- a/frontend/src/components/Quiz.js +++ b/frontend/src/components/Quiz.js @@ -3,20 +3,184 @@ import axios from 'axios'; const Quiz = ({ sessionId, onGoHome, onRetry }) => { const API_URL = process.env.REACT_APP_API_URL; + + // Quiz state const [questionData, setQuestionData] = useState(null); - const [selectedAnswers, setSelectedAnswers] = useState([]); - const [userAnswer, setUserAnswer] = useState(''); + const [quizCompleted, setQuizCompleted] = useState(false); const [totalQuestions, setTotalQuestions] = useState(0); const [correctAnswerCount, setCorrectAnswersCount] = useState(0); const [incorrectAnswersCount, setIncorrectAnswersCount] = useState(0); + + // Answer state + const [selectedAnswers, setSelectedAnswers] = useState([]); + const [userAnswer, setUserAnswer] = useState(''); const [correctAnswers, setCorrectAnswers] = useState(''); - const [quizCompleted, setQuizCompleted] = useState(false); - const [timer, setTimer] = useState(0); - const [timerId, setTimerId] = useState(null); + + // UI state const [feedback, setFeedback] = useState(''); const [isFeedbackVisible, setIsFeedbackVisible] = useState(false); const [fadeOut, setFadeOut] = 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 const pageStyle = { @@ -118,160 +282,7 @@ const Quiz = ({ sessionId, onGoHome, onRetry }) => { transition: 'opacity 0.5s ease-out, transform 0.5s ease-out', }; - // Keyboard event handler - 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}`; - }; - + // Render quiz completion screen if (quizCompleted) { const totalAnswered = correctAnswerCount + (totalQuestions - correctAnswerCount); const percentageCorrect = totalAnswered > 0 ? (correctAnswerCount / (correctAnswerCount + incorrectAnswersCount)) * 100 : 0; @@ -306,8 +317,10 @@ const Quiz = ({ sessionId, onGoHome, onRetry }) => { ); } + // Loading state if (!questionData) return
Loading...
; + // Quiz question rendering const hasMultipleOptions = questionData.options.length > 1; const hasMultipleCorrectAnswers = questionData.multiple_choice;