diff --git a/client/components/Tests/ReadingTest/ReadingTest.js b/client/components/Tests/ReadingTest/ReadingTest.js index caaa9f99..01036dc8 100644 --- a/client/components/Tests/ReadingTest/ReadingTest.js +++ b/client/components/Tests/ReadingTest/ReadingTest.js @@ -3,21 +3,22 @@ import { useSelector, useDispatch } from 'react-redux' import { Segment } from 'semantic-ui-react' import { Spinner, Button } from 'react-bootstrap' import { useHistory } from 'react-router-dom' -import { - sendReadingTestAnswer, - finishExhaustiveTest, - updateTestFeedbacks, - updateReadingTestElicitation, - nextReadingTestQuestion, - finishReadingTest, - markAnsweredChoice, - sendReadingTestQuestionnaireResponses, +import { + resetTests, + sendReadingTestAnswer, + getReadingHistory, + updateTestFeedbacks, + updateReadingTestElicitation, + nextReadingTestQuestion, + finishReadingTest, + markAnsweredChoice, + sendReadingTestQuestionnaireResponses, } from 'Utilities/redux/testReducer' import { getGroups } from 'Utilities/redux/groupsReducer' -import { - learningLanguageSelector, - confettiRain, - hiddenFeatures +import { + learningLanguageSelector, + confettiRain, + hiddenFeatures } from 'Utilities/common' import { FormattedMessage, FormattedHTMLMessage } from 'react-intl' import ReadingTestMC from './ReadingTestMC' @@ -41,24 +42,18 @@ const ReadingTest = () => { const [showCorrect, setShowCorrect] = useState(false) const [questionDone, setQuestionDone] = useState(false) const [currentAnswer, setCurrentAnswer] = useState(null) - const [attempts, setAttempts] = useState(0) + const [attempts, setAttempts] = useState(0) const [showFeedbacks, setShowFeedbacks] = useState(false) const [currentReadingSetLength, setCurrentReadingSetLength] = useState(0) // const [firstMediationSelfReflectionDone, setFirstMediationSelfReflectionDone] = useState(false) const [showSelfReflect, setShowSelfReflect] = useState(false) - + const [showElicitDialog, setShowElicitDialog] = useState(false) const [currentElicatedConstruct, setCurrentElicatedConstruct] = useState(null) - const [correctFirstAttempt, setCorrectFirstAttempt] = useState(0) - const [totalQuestions, setTotalQuestions] = useState(0) - const [correctAfterHints, setCorrectAfterHints] = useState(0) - const [totalHints, setTotalHints] = useState(0) const [hintsUsedThisQuestion, setHintsUsedThisQuestion] = useState(0) - const [startTime] = useState(Date.now()) - const [timeSpent, setTimeSpent] = useState(0) const [showStats, setShowStats] = useState(false) // State for experimental and control groups @@ -66,6 +61,7 @@ const ReadingTest = () => { const [in_control_grp, setInControlGrp] = useState(false); const { + pending, feedbacks, currentReadingTestQuestion, currentReadingSet, @@ -79,6 +75,8 @@ const ReadingTest = () => { answerFailure, resumedTest, readingTestSetDict, + readingHistory, + testDone, } = useSelector(({ tests }) => tests) const learningLanguage = useSelector(learningLanguageSelector) const { groups } = useSelector(({ groups }) => groups) @@ -87,28 +85,8 @@ const ReadingTest = () => { const dispatch = useDispatch() - const nextQuestion = () => { - setShowCorrect(false) - setQuestionDone(false) - setShowFeedbacks(false) - setShowElicitDialog(false) - setShowSelfReflect(false) - setCurrentAnswer(null) - setCurrentElicatedConstruct(null) - setAttempts(0) - - setTotalQuestions(prev => prev + 1) // Increase the total questions count - - if (currentReadingQuestionIndex === readingTestQuestions.length - 1){ - dispatch(finishReadingTest()) - setShowStats(true) - } else { - dispatch(nextReadingTestQuestion()) - } - setHintsUsedThisQuestion(0) // Reset hints count for the next question - } - const restartTest = () => { + dispatch(resetTests()) dispatch(getReadingTestQuestions(learningLanguage, false)); }; @@ -118,12 +96,14 @@ const ReadingTest = () => { const submitSelfReflectionResponse = (response_json) => { dispatch(sendReadingTestQuestionnaireResponses(response_json, learningLanguage)) - if (response_json.is_end_set_questionair == true){ - if (currentReadingQuestionIndex === readingTestQuestions.length - 1){ + if (response_json.is_end_set_questionair == true) { + if (currentReadingQuestionIndex === readingTestQuestions.length - 1) { goToHomePage() - } else { - setShowNextSetDialog(true) } + // else { + // // the self reflection does not show after every set - need to move this is to somewhere else + // setShowNextSetDialog(true) + // } } // else { // setFirstMediationSelfReflectionDone(true) @@ -141,59 +121,42 @@ const ReadingTest = () => { } const checkAnswer = choice => { + console.log("checkAnswer", choice) if (!currentReadingTestQuestion) return - + if (in_control_grp) { setAttempts(prev => prev + 1) } - + setCurrentAnswer(choice) - + const countNotSelectedChoices = currentReadingTestQuestion.choices.filter(choice => choice.isSelected != true).length; const question_concept_feedbacks = currentReadingTestQuestion.question_concept_feedbacks[currentElicatedConstruct] - - if (choice.is_correct){ + + if (choice.is_correct) { if (in_experimental_grp) { - confettiRain(0,0.45,60) - confettiRain(1,0.45,120) + confettiRain(0, 0.45, 60) + confettiRain(1, 0.45, 120) } - - if (hintsUsedThisQuestion === 0) { - setCorrectFirstAttempt(prev => prev + 1) // Track correct on first attempt - } - + if (in_experimental_grp) { - if (countNotSelectedChoices >= currentReadingTestQuestion.choices.length){ + if (countNotSelectedChoices >= currentReadingTestQuestion.choices.length) { dispatch(updateTestFeedbacks(choice.option, ["Correct!"])) } else { - if (question_concept_feedbacks && question_concept_feedbacks?.synthesis){ + if (question_concept_feedbacks && question_concept_feedbacks?.synthesis) { dispatch(updateTestFeedbacks(choice.option, question_concept_feedbacks?.synthesis)) } } } - + setShowCorrect(true) setQuestionDone(true) setCurrentAnswer(null) - - dispatch( - sendReadingTestAnswer( - learningLanguage, - readingTestSessionId, - { - type: currentReadingTestQuestion.type, - question_id: currentReadingTestQuestion.question_id, - answer: choice.option, - seenFeedbacks: feedbacks, - questionDone: true, - } - ) - ) } else { if (in_experimental_grp) { setHintsUsedThisQuestion(prev => prev + 1) // Increment the hints used } - + if (choice.is_correct && hintsUsedThisQuestion > 0) { setCorrectAfterHints(prev => prev + 1) // Track correct after using hints setTotalHints(prev => prev + hintsUsedThisQuestion) // Add to total hints count @@ -205,8 +168,8 @@ const ReadingTest = () => { : false; let markQuestionDone = questionDone; - if (choice.is_correct == false && in_experimental_grp){ - if (question_concept_feedbacks === undefined || currentReadingTestQuestion.eliciated_construct === undefined){ + if (choice.is_correct == false && in_experimental_grp) { + if (question_concept_feedbacks === undefined || currentReadingTestQuestion.eliciated_construct === undefined) { setShowElicitDialog(true) } else { const synthesis_feedback = question_concept_feedbacks && question_concept_feedbacks?.synthesis @@ -216,21 +179,21 @@ const ReadingTest = () => { ? Object.entries(currentReadingTestQuestion.item_feedbacks) .filter(([, value]) => value !== undefined) .map(([, value]) => value) - : []; + : []; const mediationFeedbacks = question_concept_feedbacks ? Object.entries(question_concept_feedbacks) - .filter(([key]) => key.startsWith('mediation_')) - .map(([, value]) => value) + .filter(([key]) => key.startsWith('mediation_')) + .map(([, value]) => value) : []; - - if (choice.is_correct == false){ - if (countNotSelectedChoices > 2){ + + if (choice.is_correct == false) { + if (countNotSelectedChoices > 2) { const remainItemFeedbacks = itemFeedbacks.filter(feedback => !feedbacks.includes(feedback)); const remainMediationFeedbacks = mediationFeedbacks.filter(feedback => !feedbacks.includes(feedback)); - if (remainMediationFeedbacks.length > 0){ + if (remainMediationFeedbacks.length > 0) { dispatch(updateTestFeedbacks(choice.option, remainMediationFeedbacks[0])) setReceivedFeedback(receivedFeedback + 1) - } else { + } else { if (remainItemFeedbacks.length > 0) { dispatch(updateTestFeedbacks(choice.option, remainItemFeedbacks[0])) setReceivedFeedback(receivedFeedback + 1) @@ -252,18 +215,18 @@ const ReadingTest = () => { } } - if (!isSelectedChoice){ + if (!isSelectedChoice) { dispatch( sendReadingTestAnswer( - learningLanguage, - readingTestSessionId, - { - type: currentReadingTestQuestion.type, - question_id: currentReadingTestQuestion.question_id, - answer: choice.option, - seenFeedbacks: feedbacks, - questionDone: markQuestionDone - } + learningLanguage, + readingTestSessionId, + { + type: currentReadingTestQuestion.type, + question_id: currentReadingTestQuestion.question_id, + answer: choice.option, + seenFeedbacks: feedbacks, + questionDone: choice.is_correct ? true : markQuestionDone + } ) ) dispatch(markAnsweredChoice(choice.option)) @@ -277,9 +240,41 @@ const ReadingTest = () => { } } } - + + const nextQuestion = () => { + console.log("nextQuestion") + setShowCorrect(false) + setQuestionDone(false) + setShowFeedbacks(false) + setShowElicitDialog(false) + setShowSelfReflect(false) + setCurrentAnswer(null) + setCurrentElicatedConstruct(null) + setAttempts(0) + + + if (currentReadingQuestionIndex === readingTestQuestions.length - 1) { + console.log("finish") + dispatch(finishReadingTest()) + dispatch(getReadingHistory(learningLanguage, readingTestSessionId)); + } else { + console.log("next") + dispatch(nextReadingTestQuestion()) + setHintsUsedThisQuestion(0) // Reset hints count for the next question + } + } + useEffect(() => { - dispatch(getGroups()); + if (readingHistory != undefined & testDone) { + setShowStats(true); + } + }, [readingHistory]); + + useEffect(() => { + dispatch(getGroups()); + if (learningLanguage && readingTestSessionId && testDone && readingHistory == {}) { + dispatch(getReadingHistory(learningLanguage, readingTestSessionId)); + } }, []); useEffect(() => { @@ -306,13 +301,6 @@ const ReadingTest = () => { setInControlGrp(control); }, [groups]); - useEffect(() => { - if (currentReadingQuestionIndex === readingTestQuestions.length - 1) { - const endTime = Date.now() - setTimeSpent(Math.floor((endTime - startTime) / 60000)) // Time in minutes - } - }, [currentReadingQuestionIndex, readingTestQuestions.length, startTime]) - useEffect(() => { setCurrentReadingSetLength(readingSetLength) }, [readingSetLength]); @@ -322,8 +310,9 @@ const ReadingTest = () => { checkAnswer(currentAnswer) } }, [currentElicatedConstruct]); - + useEffect(() => { + console.log("currentReadingSet", currentReadingSet) setShowFeedbacks(false) if (currentReadingSet !== null && prevReadingSet !== null && currentReadingSet !== prevReadingSet) { const prevSet = readingTestSetDict[prevReadingSet] @@ -336,11 +325,13 @@ const ReadingTest = () => { } } } - // setFirstMediationSelfReflectionDone(resumedTest) + if (prevReadingSet !== null) { + setShowNextSetDialog(true) + } }, [currentReadingSet]) useEffect(() => { - if (feedbacks.length == 0){ + if (feedbacks.length == 0) { setShowFeedbacks(false) } else { setShowFeedbacks(true) @@ -350,13 +341,11 @@ const ReadingTest = () => { useEffect(() => { if (!readingTestSessionId) return if (!currentReadingTestQuestion) { - dispatch(finishExhaustiveTest(learningLanguage, readingTestSessionId)) - } + dispatch(finishReadingTest(learningLanguage, readingTestSessionId)) + } setCurrentElicatedConstruct(currentReadingTestQuestion ? currentReadingTestQuestion.eliciated_construct : null) }, [currentReadingTestQuestion]) - useEffect(() => () => checkAnswer(''), []) - if (!currentReadingTestQuestion) { return null } @@ -364,19 +353,8 @@ const ReadingTest = () => { const testContainerOverflow = displaySpinner ? { overflow: "hidden" } : { overflowY: "auto" }; if (showStats) { - const firstAttemptCorrectRate = (correctFirstAttempt / totalQuestions) * 100 - const overallCorrectRate = ((correctFirstAttempt + correctAfterHints) / totalQuestions) * 100 - const avgHintsUsed = totalHints / correctAfterHints - return ( - + ) } @@ -384,9 +362,9 @@ const ReadingTest = () => {
-
+
setShowNextSetDialog(false)} /> - { setShowFeedbacks(false) @@ -395,7 +373,7 @@ const ReadingTest = () => { // } }} /> - { receieved_feedback={receivedFeedback} submitSelfReflection={submitSelfReflectionResponse} /> - { className="next-reading-question-button btn-secondary" style={{ marginLeft: '0.5em' }} onClick={() => nextQuestion()} - disabled={!questionDone || showFeedbacks } + disabled={!questionDone || showFeedbacks} > {currentReadingQuestionIndex === readingTestQuestions.length - 1 ? ( diff --git a/client/components/Tests/ReadingTest/ReadingTestElicitationDialog.js b/client/components/Tests/ReadingTest/ReadingTestElicitationDialog.js index 49820b35..d5ed891e 100644 --- a/client/components/Tests/ReadingTest/ReadingTestElicitationDialog.js +++ b/client/components/Tests/ReadingTest/ReadingTestElicitationDialog.js @@ -5,7 +5,7 @@ import { useSelector } from 'react-redux'; import { sanitizeHtml } from 'Utilities/common'; import { FormattedMessage, useIntl } from 'react-intl' import useWindowDimensions from 'Utilities/windowDimensions'; - + const ReadingTestElicationDialog = ({ question, showElication, submitElication }) => { const bigScreen = useWindowDimensions().width >= 700; @@ -42,14 +42,20 @@ const ReadingTestElicationDialog = ({ question, showElication, submitElication } paddingRight: '1em', width: '100%' } - }> + }>

- + {chunks}, + i: (chunks) => {chunks}, + }} + />

{Object.keys(question.question_concept_feedbacks).map((key, index) => ( - diff --git a/client/components/Tests/ReadingTest/index.js b/client/components/Tests/ReadingTest/index.js index c7fbe98f..a1674fd0 100644 --- a/client/components/Tests/ReadingTest/index.js +++ b/client/components/Tests/ReadingTest/index.js @@ -21,14 +21,14 @@ const ReadingTestView = () => { const dispatch = useDispatch() const learningLanguage = useLearningLanguage() const [sessionToDelete, setSessionToDelete] = useState(false) - const { readingTestSessionId, pending, lastReadingSessionFinished } = useSelector( + const { readingTestSessionId, pending, testDone } = useSelector( ({ tests }) => tests ) const bigScreen = useWindowDimension().width >= 650 const [showDDLangIntroductory, setShowDDLangIntroductory] = useState(false) useEffect(() => { - dispatch(getReadingTestQuestions(learningLanguage, lastReadingSessionFinished==false)) + dispatch(getReadingTestQuestions(learningLanguage, !testDone)) }, [dispatch, learningLanguage]) if (pending) { diff --git a/client/util/redux/testReducer.js b/client/util/redux/testReducer.js index 460d4e79..a355744c 100644 --- a/client/util/redux/testReducer.js +++ b/client/util/redux/testReducer.js @@ -14,12 +14,13 @@ const initialState = { readingSetLength: 0, readingTestQuestions: [], readingTestSetDict: {}, + readingHistory: {}, exhaustiveTestSessionId: null, currentExhaustiveQuestionIndex: 0, currentExhaustiveTestQuestion: null, exhaustiveTestQuestions: [], - + adaptiveTestSessionId: null, currentAdaptiveQuestionIndex: 0, adaptiveTestResults: null, @@ -27,6 +28,7 @@ const initialState = { timedTest: true, report: null, feedbacks: [], + testDone: false, } const clearLocalStorage = () => { @@ -35,8 +37,7 @@ const clearLocalStorage = () => { window.localStorage.removeItem('testLanguage') } -export const getReadingTestQuestions = (language, is_continue=true) => { - console.log("is_continue", is_continue) +export const getReadingTestQuestions = (language, is_continue = true) => { const route = `/test/${language}/reading` const prefix = 'GET_READING_TEST_QUESTIONS' const query = { @@ -92,7 +93,7 @@ export const sendReadingTestAnswer = (language, sessionId, answer) => { } export const sendReadingTestQuestionnaireResponses = (reflection_response, language) => { - const route = `/questionnaire/${language}`; + const route = `/questionnaire/${language}`; const prefix = 'SEND_READING_TEST_QUESTIONNAIRE_RESPONSES'; const payload = reflection_response; return callBuilder(route, prefix, 'post', payload); @@ -149,6 +150,13 @@ export const finishExhaustiveTest = (language, sessionId) => { return callBuilder(route, prefix, 'post', payload) } +export const getReadingHistory = (language, sessionId) => { + console.log("getReadingHistory", sessionId) + const route = `/test/${language}/reading/history?session_id=${sessionId}` + const prefix = 'GET_READING_TEST_HISTORY' + return callBuilder(route, prefix, 'get') +} + export const getHistory = language => { const now = moment().format('YYYY-MM-DD') const route = `/test/${language}/history?start_time=2019-01-01&end_time=${now}` @@ -170,7 +178,7 @@ export const resetTests = () => { } export const updateTestFeedbacks = (answer, feedbacks) => ({ - type: 'UPDATE_TEST_FEEDBACKS', answer, feedbacks + type: 'UPDATE_TEST_FEEDBACKS', answer, feedbacks }) export const updateReadingTestElicitation = (eliciated_construct) => ({ @@ -186,10 +194,10 @@ export const finishReadingTest = () => ({ type: 'FINISH_READING_TEST' }) export const markAnsweredChoice = (answer) => ({ type: 'MARK_ANSWERED_CHOICE', answer }) export default (state = initialState, action) => { - const { + const { currentAdaptiveQuestionIndex, currentExhaustiveQuestionIndex, exhaustiveTestQuestions, - currentReadingQuestionIndex, readingTestQuestions, + currentReadingQuestionIndex, readingTestQuestions, currentReadingSet, prevReadingSet, currentQuestionIdxinSet } = state const { response, startingIndex } = action @@ -210,7 +218,7 @@ export default (state = initialState, action) => { } case 'GET_READING_TEST_QUESTIONS_SUCCESS': const { question_list, session_id, question_set_dict } = response; - + // Split questions by set const questionsBySet = question_list.reduce((acc, question) => { const set = question.set; @@ -224,10 +232,10 @@ export default (state = initialState, action) => { } return acc; }, {}); - + // Sort sets by set number const sortedSets = Object.keys(questionsBySet).sort((a, b) => parseInt(a) - parseInt(b)); - + // Find the current question let tmpcurrentReadingTestQuestion = null; let currentSet = null; @@ -235,7 +243,7 @@ export default (state = initialState, action) => { let tmpcurrentReadingQuestionIndex = -1; let tmpreadingSetLength = 0; let tempreadingTestQuestions = [] - + let tmp_reading_question_idx = 0 let current_question_is_set = false for (const set of sortedSets) { @@ -253,15 +261,15 @@ export default (state = initialState, action) => { tmp_reading_question_idx = seen.length } } - + // Calculate previous reading set const prevReadingSet = currentSet && parseInt(currentSet) > 1 ? parseInt(currentSet) - 1 : null; - + // If the current question has only one construct, set the eliciated_construct field if (tmpcurrentReadingTestQuestion?.constructs?.length === 1) { tmpcurrentReadingTestQuestion.eliciated_construct = tmpcurrentReadingTestQuestion.constructs[0]; } - + return { ...state, readingTestSetDict: question_set_dict, @@ -275,9 +283,11 @@ export default (state = initialState, action) => { feedbacks: [], readingSetLength: tmpreadingSetLength, pending: false, - resumedTest: Object.values(questionsBySet).some(x=>x.seen.length>0), + resumedTest: Object.values(questionsBySet).some(x => x.seen.length > 0), + testDone: tempreadingTestQuestions.filter(question => !question.seen).length === 0, }; - + + case 'GET_READING_TEST_QUESTIONS_FAILURE': return { ...state, @@ -349,7 +359,7 @@ export default (state = initialState, action) => { resumePending: false, answerFailure: true, } - + case 'NEXT_TEST_QUESTION': return { ...state, @@ -359,21 +369,81 @@ export default (state = initialState, action) => { } case 'FINISH_READING_TEST': - let _lastSet = readingTestQuestions[currentReadingQuestionIndex].set; + let _lastSet = readingTestQuestions[currentReadingQuestionIndex]?.set; let _finishedSet = _lastSet + 1; return { ...state, currentReadingSet: _finishedSet, prevReadingSet: _lastSet, lastReadingSessionFinished: true, + testDone: true + } + + case 'GET_READING_TEST_HISTORY_ATTEMPT': + return { + ...state, + pending: true, + error: false, + } + + case 'GET_READING_TEST_HISTORY_SUCCESS': { + const { history } = response; + + // Initialize variables for tracking the statistics + let totalQuestions = 0; + let firstTimeCorrectCount = 0; + let overallCorrectCount = 0; + + console.log("history", history) + + // Iterate through each question's history + for (const questionId in history) { + const attempts = history[questionId].attempts; + + if (attempts.length > 0) { + totalQuestions += 1; + + // Check if the first attempt was correct + if (attempts[0].correct) { + firstTimeCorrectCount += 1; + } + + // Check if any attempt was correct + if (attempts.some(attempt => attempt.correct)) { + overallCorrectCount += 1; + } + } + } + + // Calculate rates + const firstTimeCorrectRate = totalQuestions > 0 ? (firstTimeCorrectCount / totalQuestions) * 100 : 0; + const overallCorrectRate = totalQuestions > 0 ? (overallCorrectCount / totalQuestions) * 100 : 0; + + // Return the updated state + return { + ...state, + pending: false, + readingHistory: { + total_num_question: totalQuestions, + first_time_answer_correct_rate: firstTimeCorrectRate, + overall_correct_rate: overallCorrectRate, + }, + }; + } + + case 'GET_READING_TEST_HISTORY_FAILURE': + return { + ...state, + pending: false, + error: true, } case 'NEXT_READING_TEST_QUESTION': - if (currentReadingQuestionIndex < readingTestQuestions.length - 1){ + if (currentReadingQuestionIndex < readingTestQuestions.length - 1) { let _currentReadingSet = readingTestQuestions[currentReadingQuestionIndex + 1].set; let _prevReadingSet = readingTestQuestions[currentReadingQuestionIndex].set; let currentReadingTestQuestion = readingTestQuestions[currentReadingQuestionIndex + 1]; - if (currentReadingTestQuestion?.constructs?.length === 1) { + if (currentReadingTestQuestion?.constructs?.length === 1) { currentReadingTestQuestion.eliciated_construct = currentReadingTestQuestion.constructs[0]; } let readingSetLength = readingTestQuestions.filter(question => question.set === _currentReadingSet).length; @@ -388,7 +458,7 @@ export default (state = initialState, action) => { readingSetLength: readingSetLength, } } - + case 'ANSWER_TEST_QUESTION_ATTEMPT': return { ...state, @@ -466,7 +536,7 @@ export default (state = initialState, action) => { } case 'UPDATE_READING_TEST_QUESTION_ELICITATION': - if (state.currentReadingTestQuestion) { + if (state.currentReadingTestQuestion) { return { ...state, currentReadingTestQuestion: { @@ -485,18 +555,18 @@ export default (state = initialState, action) => { return { ...choice }; } }); - + const updatedCurrentReadingTestQuestion = { ...state.currentReadingTestQuestion, choices: updatedChoices, }; - + return { ...state, currentReadingTestQuestion: updatedCurrentReadingTestQuestion, }; } - + default: return state }