-u level & exercise hooks
This commit is contained in:
parent
6a48573052
commit
be9aba3355
|
|
@ -21,8 +21,6 @@ import { SlugProvider } from '../../utils/SlugContext';
|
||||||
import '../../assets/styles/user.css';
|
import '../../assets/styles/user.css';
|
||||||
import useAuth from '../guest/auth/hooks/useAuth';
|
import useAuth from '../guest/auth/hooks/useAuth';
|
||||||
|
|
||||||
import ExerciseTest from './exerciseCombine/views/Exercise';
|
|
||||||
|
|
||||||
const UserRoutes = () => {
|
const UserRoutes = () => {
|
||||||
const { logout } = useAuth();
|
const { logout } = useAuth();
|
||||||
return (
|
return (
|
||||||
|
|
@ -44,8 +42,6 @@ const UserRoutes = () => {
|
||||||
<Route path="module/:section/:topic/:level/material" element={<Material />} />
|
<Route path="module/:section/:topic/:level/material" element={<Material />} />
|
||||||
<Route path="module/:section/:topic/:level/exercise" element={<Exercise />} />
|
<Route path="module/:section/:topic/:level/exercise" element={<Exercise />} />
|
||||||
<Route path="module/:section/:topic/:level/result" element={<ExerciseResult />} />
|
<Route path="module/:section/:topic/:level/result" element={<ExerciseResult />} />
|
||||||
|
|
||||||
<Route path="module/:section/:topic/:level/exercise-test" element={<ExerciseTest />} />
|
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</UserLayout>
|
</UserLayout>
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export const useExercises = (topic, level) => {
|
||||||
|
|
||||||
const data = await exerciseService.fetchExercise(getLevelId);
|
const data = await exerciseService.fetchExercise(getLevelId);
|
||||||
setQuestions(data);
|
setQuestions(data);
|
||||||
if (localStorage.getItem(getLevelId)) {
|
if (localStorage.getItem(stdLearning.payload.ID_STUDENT_LEARNING)) {
|
||||||
// const savedAnswers = JSON.parse(localStorage.getItem(getLevelId));
|
// const savedAnswers = JSON.parse(localStorage.getItem(getLevelId));
|
||||||
const savedAnswers = JSON.parse(localStorage.getItem(stdLearning.payload.ID_STUDENT_LEARNING));
|
const savedAnswers = JSON.parse(localStorage.getItem(stdLearning.payload.ID_STUDENT_LEARNING));
|
||||||
setAnswers(savedAnswers);
|
setAnswers(savedAnswers);
|
||||||
|
|
@ -92,7 +92,7 @@ export const useExercises = (topic, level) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirmSubmit = async () => {
|
const handleConfirmSubmit = async () => {
|
||||||
const answer = JSON.parse(localStorage.getItem(levelId));
|
const answer = JSON.parse(localStorage.getItem(learningId));
|
||||||
try {
|
try {
|
||||||
const sendAnswer = await exerciseService.sumbitAnswer({ answers: answer });
|
const sendAnswer = await exerciseService.sumbitAnswer({ answers: answer });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ const fetchExercise = async (idLevel) => {
|
||||||
|
|
||||||
return sortedData;
|
return sortedData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching exercise data:', error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ const Exercise = () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) return <p>Error loading questions.</p>;
|
if (error) return <h1 className='text-center'>Exercise questions not yet available</h1>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container fluid className='exercise-page'>
|
<Container fluid className='exercise-page'>
|
||||||
|
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import exerciseService from '../services/exerciseService';
|
|
||||||
import { useSlugContext } from '../../../../utils/SlugContext';
|
|
||||||
import { unSlugify } from '../../../../utils/Constant';
|
|
||||||
|
|
||||||
export const useExercise = (topic, level) => {
|
|
||||||
const [questions, setQuestions] = useState([]);
|
|
||||||
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
|
||||||
const [answers, setAnswers] = useState({});
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
const [error, setError] = useState(null);
|
|
||||||
const [levelId, setLevelId] = useState(null);
|
|
||||||
const [learningId, setLearningId] = useState(null);
|
|
||||||
const { topicSlugMap } = useSlugContext();
|
|
||||||
|
|
||||||
const fetchData = async () => {
|
|
||||||
try {
|
|
||||||
const getLevelId = await exerciseService.getLevelId(topicSlugMap[topic], unSlugify(level));
|
|
||||||
setLevelId(getLevelId);
|
|
||||||
const stdLearning = await exerciseService.checkStdLearning(getLevelId);
|
|
||||||
if (stdLearning === null) {
|
|
||||||
const newLearningId = await exerciseService.createStdLearning({ ID_LEVEL: getLevelId });
|
|
||||||
setLearningId(newLearningId);
|
|
||||||
}else{
|
|
||||||
setLearningId(stdLearning);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const data = await exerciseService.fetchExercise(getLevelId);
|
|
||||||
setQuestions(data);
|
|
||||||
if (localStorage.getItem(getLevelId)) {
|
|
||||||
const savedAnswers = JSON.parse(localStorage.getItem(getLevelId));
|
|
||||||
setAnswers(savedAnswers);
|
|
||||||
}else{
|
|
||||||
setAnswers(new Array(data.length).fill(null));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("something wrong : ", error);
|
|
||||||
setError(error);
|
|
||||||
}finally{
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
}, [topic, level]);
|
|
||||||
|
|
||||||
const handleAnswer = (index, answer) => {
|
|
||||||
// const newAnswers = { ...answers, [index]: answer };
|
|
||||||
const newAnswers = [...answers];
|
|
||||||
newAnswers[index] = answer;
|
|
||||||
setAnswers(newAnswers);
|
|
||||||
localStorage.setItem(levelId, JSON.stringify(newAnswers));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMatchingPairs = (questionId, pairs) => {
|
|
||||||
const isComplete = pairs.every(pair => pair.left && pair.right);
|
|
||||||
if (isComplete) {
|
|
||||||
handleAnswer(questionId, pairs);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const nextQuestion = () => {
|
|
||||||
if (currentQuestionIndex < questions.length - 1) {
|
|
||||||
setCurrentQuestionIndex(currentQuestionIndex + 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const prevQuestion = () => {
|
|
||||||
if (currentQuestionIndex > 0) {
|
|
||||||
setCurrentQuestionIndex(currentQuestionIndex - 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleKeyDown = (event) => {
|
|
||||||
if (event.key === 'ArrowLeft') {
|
|
||||||
prevQuestion();
|
|
||||||
} else if (event.key === 'ArrowRight') {
|
|
||||||
nextQuestion();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
|
||||||
};
|
|
||||||
}, [prevQuestion, nextQuestion]);
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
const allAnswered = answers.every((answer) => answer !== null);
|
|
||||||
return allAnswered;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConfirmSubmit = () => {
|
|
||||||
setShowModal(false);
|
|
||||||
navigate(`/learning/module/${section}/${topic}/${level}/result`, {
|
|
||||||
state: { answers }
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
questions,
|
|
||||||
currentQuestion: questions[currentQuestionIndex],
|
|
||||||
currentQuestionIndex,
|
|
||||||
setCurrentQuestionIndex,
|
|
||||||
answers,
|
|
||||||
isLoading,
|
|
||||||
error,
|
|
||||||
handleAnswer,
|
|
||||||
handleMatchingPairs,
|
|
||||||
nextQuestion,
|
|
||||||
prevQuestion,
|
|
||||||
handleSubmit,
|
|
||||||
handleConfirmSubmit
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
import axios from 'axios';
|
|
||||||
import { API_URL } from '../../../../utils/Constant';
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
headers: {
|
|
||||||
Authorization: localStorage.getItem('token')
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSoalNumber = (title) => {
|
|
||||||
const match = title.match(/\d+$/); // Mencari angka di akhir string
|
|
||||||
return match ? parseInt(match[0], 10) : 0; // Mengembalikan angka atau 0 jika tidak ditemukan
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk cek apakah std_learning sudah ada
|
|
||||||
const checkStdLearning = async (level) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${API_URL}/stdLearning/level/${level}`, config);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
// console.error('Error checking std_learning:', error);
|
|
||||||
// throw error;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getLevelId = async (topicId, levelName) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${API_URL}/level/topic/${topicId}`, config);
|
|
||||||
const filteredData = response.data.data.levels.filter(item => item.NAME_LEVEL === levelName);
|
|
||||||
return filteredData[0].ID_LEVEL;
|
|
||||||
} catch (error) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk membuat std_learning
|
|
||||||
const createStdLearning = async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.post(`${API_URL}/stdLearning`, data, config);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error creating std_learning:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk mendapatkan data exercise
|
|
||||||
const fetchExercise = async (idLevel) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${API_URL}/exercise/level/${idLevel}`, config);
|
|
||||||
// return response.data;
|
|
||||||
|
|
||||||
const sortedData = response.data.payload.sort((a, b) => {
|
|
||||||
return getSoalNumber(a.TITLE) - getSoalNumber(b.TITLE);
|
|
||||||
});
|
|
||||||
|
|
||||||
return sortedData;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching exercise data:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getLevelId,
|
|
||||||
checkStdLearning,
|
|
||||||
createStdLearning,
|
|
||||||
fetchExercise
|
|
||||||
};
|
|
||||||
|
|
@ -1,207 +0,0 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
import { useExercise } from '../hooks/useExercises';
|
|
||||||
import { Container, Row, Col, ListGroup, Button, Modal, Alert, OverlayTrigger, Popover } from 'react-bootstrap';
|
|
||||||
import MultipleChoiceQuestion from './components/MultipleChoiceQuestion';
|
|
||||||
import TrueFalseQuestion from './components/TrueFalseQuestion';
|
|
||||||
import MatchingPairsQuestion from './components/MatchingPairsQuestion';
|
|
||||||
import Skeleton from 'react-loading-skeleton';
|
|
||||||
|
|
||||||
import ilustration from '../../../../assets/images/illustration/submitExercise.png';
|
|
||||||
|
|
||||||
const Exercise = () => {
|
|
||||||
const [showModal, setShowModal] = useState(false);
|
|
||||||
const [showAlert, setShowAlert] = useState(false);
|
|
||||||
const { topic, level } = useParams();
|
|
||||||
const {
|
|
||||||
questions,
|
|
||||||
currentQuestion,
|
|
||||||
currentQuestionIndex,
|
|
||||||
setCurrentQuestionIndex,
|
|
||||||
answers,
|
|
||||||
isLoading,
|
|
||||||
error,
|
|
||||||
handleAnswer,
|
|
||||||
nextQuestion,
|
|
||||||
prevQuestion,
|
|
||||||
handleSubmit,
|
|
||||||
handleConfirmSubmit
|
|
||||||
} = useExercise(topic, level);
|
|
||||||
|
|
||||||
const handleSubmitWrapper = () => {
|
|
||||||
if (handleSubmit()) {
|
|
||||||
setShowModal(true);
|
|
||||||
} else {
|
|
||||||
setShowAlert(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleCloseModal = () => setShowModal(false);
|
|
||||||
|
|
||||||
const renderQuestion = () => {
|
|
||||||
switch (currentQuestion.QUESTION_TYPE) {
|
|
||||||
case 'MCQ':
|
|
||||||
return (
|
|
||||||
<MultipleChoiceQuestion
|
|
||||||
question={currentQuestion}
|
|
||||||
onAnswer={handleAnswer}
|
|
||||||
savedAnswer={answers[currentQuestionIndex]}
|
|
||||||
index={currentQuestionIndex}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case 'TFQ':
|
|
||||||
return (
|
|
||||||
<TrueFalseQuestion
|
|
||||||
question={currentQuestion}
|
|
||||||
onAnswer={handleAnswer}
|
|
||||||
savedAnswer={answers[currentQuestionIndex]}
|
|
||||||
index={currentQuestionIndex}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case 'MPQ':
|
|
||||||
return (
|
|
||||||
<MatchingPairsQuestion
|
|
||||||
question={currentQuestion}
|
|
||||||
onAnswer={handleAnswer}
|
|
||||||
savedAnswer={answers[currentQuestionIndex]}
|
|
||||||
index={currentQuestionIndex}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return <p>Unknown question type.</p>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const popover = (
|
|
||||||
<Popover id="popover-basic">
|
|
||||||
<Popover.Header as="h4">Tips</Popover.Header>
|
|
||||||
<Popover.Body className='p-2'>
|
|
||||||
<ul className='ps-3 m-0'>
|
|
||||||
<li>Click the <strong>left arrow</strong> key to go to the previous question</li>
|
|
||||||
<li>Click the <strong>right arrow</strong> key to go to the next question</li>
|
|
||||||
</ul>
|
|
||||||
</Popover.Body>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-2">
|
|
||||||
<Skeleton containerClassName='w-100' className='w-100 mb-1 rounded-3' count={6} style={{height:"4vh"}} />
|
|
||||||
</div>
|
|
||||||
<div className="col-10">
|
|
||||||
<Skeleton containerClassName='w-100' className='w-100 mb-1 rounded-3' style={{height:"5vh"}} />
|
|
||||||
<Skeleton containerClassName='w-100' className='w-50 mb-1 rounded-3' style={{height:"20vh"}} />
|
|
||||||
<Skeleton containerClassName='w-100' className='w-100 mb-1 rounded-3' style={{height:"30vh"}} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) return <p>Error loading questions.</p>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container fluid className='exercise-page'>
|
|
||||||
<Row>
|
|
||||||
<Col sm={2}>
|
|
||||||
<div className='p-3 rounded-4 bg-white'>
|
|
||||||
<div className="mb-3 d-flex justify-content-between align-items-center">
|
|
||||||
<h4 className='mb-0 text-gd fw-bold'>Pretest</h4>
|
|
||||||
<OverlayTrigger trigger="click" placement="right" overlay={popover}>
|
|
||||||
<i className=" bi bi-info-circle cursor-pointer text-secondary"></i>
|
|
||||||
</OverlayTrigger>
|
|
||||||
</div>
|
|
||||||
<ListGroup variant="flush" className='number-list'>
|
|
||||||
{questions.map((q, index) => (
|
|
||||||
<ListGroup.Item
|
|
||||||
key={q.ID_ADMIN_EXERCISE}
|
|
||||||
active={index === currentQuestionIndex}
|
|
||||||
onClick={() => setCurrentQuestionIndex(index)}
|
|
||||||
className={`border-0 rounded-3 number-label ${answers[index] !== null ? 'answered fw-bold' : ''}`}
|
|
||||||
style={{ cursor: 'pointer' }}
|
|
||||||
>
|
|
||||||
<i className={`me-2 bi bi-circle ${answers[index] !== null ? 'd-none' : 'd-block'}`}></i>
|
|
||||||
<i className={`me-2 bi bi-check2-circle ${answers[index] !== null ? 'd-block' : 'd-none'}`}></i>
|
|
||||||
{index+1}
|
|
||||||
</ListGroup.Item>
|
|
||||||
))}
|
|
||||||
</ListGroup>
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col sm={10}>
|
|
||||||
<div className='p-4 rounded-4 bg-white'>
|
|
||||||
<div className="pb-4 d-flex justify-content-between align-items-center">
|
|
||||||
<Button
|
|
||||||
variant='outline-blue'
|
|
||||||
className={`rounded-35 ${currentQuestionIndex === 0 ? 'invisible' : 'visible'}`}
|
|
||||||
onClick={prevQuestion}
|
|
||||||
disabled={currentQuestionIndex === 0}
|
|
||||||
>
|
|
||||||
<i className="bi bi-arrow-left"></i>
|
|
||||||
</Button>
|
|
||||||
<h5 className='m-0'>{`Questions ${currentQuestionIndex + 1} of ${questions.length}`}</h5>
|
|
||||||
<Button
|
|
||||||
variant="blue"
|
|
||||||
className={`rounded-35 ${currentQuestionIndex === questions.length - 1 ? 'd-none' : ''}`}
|
|
||||||
onClick={nextQuestion}
|
|
||||||
disabled={currentQuestionIndex === questions.length - 1}
|
|
||||||
>
|
|
||||||
Next Questions <i className="bi bi-arrow-right"></i>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="blue"
|
|
||||||
className={`rounded-35 px-4 ${currentQuestionIndex === questions.length - 1 ? 'd-inline-block' : 'd-none'}`}
|
|
||||||
onClick={handleSubmitWrapper}
|
|
||||||
>
|
|
||||||
Submit <i className="bi bi-send"></i>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className='p-3 border rounded-3'>
|
|
||||||
{renderQuestion()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Alert show={showAlert} variant="danger" className="custom-alert" onClose={() => setShowAlert(false)} dismissible>
|
|
||||||
<Alert.Heading>ATTENTION!</Alert.Heading>
|
|
||||||
<span>Please answer all questions before submitting.</span>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
{/* <Modal show={showModal} onHide={handleCloseModal} centered>
|
|
||||||
<Modal.Header closeButton>
|
|
||||||
<Modal.Title>Confirmation</Modal.Title>
|
|
||||||
</Modal.Header>
|
|
||||||
<Modal.Body>
|
|
||||||
<p>Are you sure you want to submit your answer?</p>
|
|
||||||
</Modal.Body>
|
|
||||||
<Modal.Footer>
|
|
||||||
<Button variant="secondary" onClick={handleCloseModal}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button variant="primary" onClick={handleConfirmSubmit}>
|
|
||||||
Yes, Submit <i className="bi bi-send"></i>
|
|
||||||
</Button>
|
|
||||||
</Modal.Footer>
|
|
||||||
</Modal> */}
|
|
||||||
|
|
||||||
<Modal show={showModal} onHide={handleCloseModal} centered>
|
|
||||||
<Modal.Body className='p-4 d-flex flex-column items-center'>
|
|
||||||
<h4 className='mb-4 fw-bold text-dark'>Proceed with <span className='text-blue'>Submission</span>?</h4>
|
|
||||||
<img src={ilustration} alt="" />
|
|
||||||
<p className='my-3 text-muted fw-light'>Confirm submission? There's no going back.</p>
|
|
||||||
<div className="mt-4 w-100 d-flex justify-content-center">
|
|
||||||
<Button variant="outline-blue" className="w-50 py-2 px-5 mx-1 rounded-35" onClick={handleCloseModal}>Check Again</Button>
|
|
||||||
<Button variant="gd" className="w-50 py-2 px-5 mx-1 rounded-35" onClick={handleConfirmSubmit}>Confirm</Button>
|
|
||||||
</div>
|
|
||||||
</Modal.Body>
|
|
||||||
</Modal>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Exercise;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const ExerciseMedia = ({ question, onAnswer, savedAnswer, index }) => {
|
|
||||||
const handleChange = (value) => {
|
|
||||||
onAnswer(index, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3>{question.TITLE}</h3>
|
|
||||||
<p>{question.QUESTION}</p>
|
|
||||||
{question.VIDEO && (
|
|
||||||
<video controls>
|
|
||||||
<source src={question.VIDEO} type="video/mp4" />
|
|
||||||
</video>
|
|
||||||
)}
|
|
||||||
{question.IMAGE && <img src={question.IMAGE} alt="Question" />}
|
|
||||||
{question.AUDIO && <audio controls src={question.AUDIO}></audio>}
|
|
||||||
<div className="options-tf">
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={`p-0 mb-3 form-check ${savedAnswer === '1' ? 'selected-answer' : ''}`}
|
|
||||||
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
onClick={() => handleChange('1')}
|
|
||||||
>
|
|
||||||
<span className="option-label">A</span>
|
|
||||||
<span className="ms-2 option-text">TRUE</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={`p-0 mb-3 form-check ${savedAnswer === '0' ? 'selected-answer' : ''}`}
|
|
||||||
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
onClick={() => handleChange('0')}
|
|
||||||
>
|
|
||||||
<span className="option-label">B</span>
|
|
||||||
<span className="ms-2 option-text">FALSE</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ExerciseMedia;
|
|
||||||
|
|
@ -1,227 +0,0 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
|
|
||||||
// const colors = ['#E9342D', '#FACC15', '#1FBC2F', '#0090FF', '#ED27D9'];
|
|
||||||
// const colors = ['#0090FF', '#FC6454', '#46E59A', '#FBD025', '#E355D5'];
|
|
||||||
const colors = ['#FC6454', '#FBD025', '#46E59A', '#0090FF','#E355D5'];
|
|
||||||
|
|
||||||
const shuffleArray = (array) => {
|
|
||||||
return array
|
|
||||||
.map((value) => ({ value, sort: Math.random() }))
|
|
||||||
.sort((a, b) => a.sort - b.sort)
|
|
||||||
.map(({ value }) => value);
|
|
||||||
};
|
|
||||||
|
|
||||||
function arrayToString(arr) {
|
|
||||||
return arr.join(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringToArray(str) {
|
|
||||||
return str.split(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
const MatchingPairsQuestion = ({ question, onAnswer, savedAnswer, index }) => {
|
|
||||||
const [pairs, setPairs] = useState([]);
|
|
||||||
const [selectedLeft, setSelectedLeft] = useState(null);
|
|
||||||
const [selectedRight, setSelectedRight] = useState(null);
|
|
||||||
const [rightOptions, setRightOptions] = useState([]);
|
|
||||||
const [isComplete, setIsComplete] = useState(false);
|
|
||||||
const [isShuffled, setIsShuffled] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleClickOutside = (event) => {
|
|
||||||
if (!event.target.closest('.mp-choice')) {
|
|
||||||
setSelectedLeft(null);
|
|
||||||
setSelectedRight(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('click', handleClickOutside);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('click', handleClickOutside);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const initialPairs = question.matchingPairs.map((pair) => ({
|
|
||||||
left: pair.LEFT_PAIR,
|
|
||||||
right: '',
|
|
||||||
color: colors[question.matchingPairs.indexOf(pair) % colors.length],
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (savedAnswer) {
|
|
||||||
const arrSavedAnswer = stringToArray(savedAnswer);
|
|
||||||
const updatedPairs = initialPairs.map((pair, index) => ({
|
|
||||||
...pair,
|
|
||||||
right: arrSavedAnswer[index],
|
|
||||||
}));
|
|
||||||
setPairs(updatedPairs);
|
|
||||||
} else {
|
|
||||||
setPairs(initialPairs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isShuffled) {
|
|
||||||
setIsShuffled(true);
|
|
||||||
setRightOptions(shuffleArray(question.matchingPairs.map((pair) => pair.RIGHT_PAIR)));
|
|
||||||
}
|
|
||||||
}, [question, savedAnswer]);
|
|
||||||
|
|
||||||
const handleLeftClick = (index) => {
|
|
||||||
setSelectedLeft(index);
|
|
||||||
const status = pairs.findIndex(item => item.right === rightOptions[selectedRight]);
|
|
||||||
if (selectedRight !== null) {
|
|
||||||
makePair(index, selectedRight, status);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRightClick = (index) => {
|
|
||||||
setSelectedRight(index);
|
|
||||||
const status = pairs.findIndex(item => item.right === rightOptions[index]);
|
|
||||||
if (selectedLeft !== null) {
|
|
||||||
makePair(selectedLeft, index, status);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const makePair = (leftIndex, rightIndex, changePair) => {
|
|
||||||
const newPairs = [...pairs];
|
|
||||||
if (changePair > -1) {
|
|
||||||
setIsComplete(false);
|
|
||||||
pairs[changePair].right = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedRightValue = rightOptions[rightIndex];
|
|
||||||
newPairs[leftIndex].right = selectedRightValue;
|
|
||||||
setPairs(newPairs);
|
|
||||||
// console.log(newPairs);
|
|
||||||
|
|
||||||
setSelectedLeft(null);
|
|
||||||
setSelectedRight(null);
|
|
||||||
|
|
||||||
const allPairsMatched = newPairs.every((pair) => pair.right !== '');
|
|
||||||
if (allPairsMatched && !isComplete) {
|
|
||||||
setIsComplete(true);
|
|
||||||
|
|
||||||
const rightAnswers = newPairs.map((pair) => pair.right);
|
|
||||||
console.log(rightAnswers);
|
|
||||||
onAnswer(index, arrayToString(rightAnswers));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{question.IMAGE !== null && (
|
|
||||||
<div className='my-1'>
|
|
||||||
{/* <img src={question.IMAGE} alt="" /> */}
|
|
||||||
<h3>{question.IMAGE}</h3>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{question.AUDIO !== null && (
|
|
||||||
<div className='my-1'>
|
|
||||||
{/* <audio controls>
|
|
||||||
<source src={question.AUDIO} type="audio/mpeg" />
|
|
||||||
Your browser does not support the audio element.
|
|
||||||
</audio> */}
|
|
||||||
<h3>{question.AUDIO}</h3>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{question.VIDEO !== null && (
|
|
||||||
// <video src={question.VIDEO} controls className='my-1'></video>
|
|
||||||
<div className="my-1">
|
|
||||||
<h3>{question.VIDEO}</h3>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<p>{question.QUESTION}</p>
|
|
||||||
<div className="w-100 options-mp d-flex justify-content-between">
|
|
||||||
{/* Bagian kiri */}
|
|
||||||
<div>
|
|
||||||
{pairs.map((pair, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className={`p-0 mb-3 form-check mp-choice`}
|
|
||||||
onClick={() => handleLeftClick(index)}
|
|
||||||
style={{
|
|
||||||
display: 'flex', alignItems: 'center', cursor: 'pointer',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="option-label"
|
|
||||||
style={{
|
|
||||||
color: "#fff",
|
|
||||||
backgroundColor: pair.color,
|
|
||||||
border: selectedLeft === index ? '2px dashed #ffffff' : '2px solid #ffffff',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{index + 1}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="ms-2 option-text"
|
|
||||||
style={{
|
|
||||||
color:
|
|
||||||
selectedLeft === index
|
|
||||||
? "#ffffff"
|
|
||||||
: (pair.right === '' ? '#000000' : "#ffffff"),
|
|
||||||
backgroundColor:
|
|
||||||
selectedLeft === index
|
|
||||||
? pair.color
|
|
||||||
: (pair.right === '' ? '#ffffff' : pair.color),
|
|
||||||
border:
|
|
||||||
selectedLeft === index
|
|
||||||
? '2px dashed #ffffff'
|
|
||||||
: (pair.right === '' ? '1px solid #000000' : '2px solid #ffffff'),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{pair.left}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Bagian kanan */}
|
|
||||||
<div>
|
|
||||||
{rightOptions.map((right, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className={`p-0 mb-3 form-check mp-choice`}
|
|
||||||
onClick={() => handleRightClick(index)}
|
|
||||||
style={{
|
|
||||||
display: 'flex', alignItems: 'center', cursor: 'pointer',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="option-label"
|
|
||||||
style={{
|
|
||||||
color: pairs.find((pair) => pair.right === right) ? '#ffffff' : '#000000',
|
|
||||||
backgroundColor:
|
|
||||||
pairs.find((pair) => pair.right === right)?.color ||
|
|
||||||
(selectedRight === index ? '#ccc' : '#ffffff'),
|
|
||||||
border:
|
|
||||||
pairs.find((pair) => pair.right === right)?.color ||
|
|
||||||
(selectedRight === index ? '2px dashed #ffffff' : '1px solid #000000'),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{index + 1}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="ms-2 option-text"
|
|
||||||
style={{
|
|
||||||
color: (pairs.find((pair) => pair.right === right) ? '#ffffff' : '#000000'),
|
|
||||||
backgroundColor:
|
|
||||||
pairs.find((pair) => pair.right === right)?.color ||
|
|
||||||
(selectedRight === index ? '#ccc' : '#ffffff'),
|
|
||||||
border:
|
|
||||||
pairs.find((pair) => pair.right === right)?.color ||
|
|
||||||
(selectedRight === index ? '2px dashed #ffffff' : '1px solid #000000'),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{right}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MatchingPairsQuestion;
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const MultipleChoiceQuestion = ({ question, onAnswer, savedAnswer, index }) => {
|
|
||||||
const options = question.multipleChoices[0];
|
|
||||||
|
|
||||||
const handleChange = (value) => {
|
|
||||||
onAnswer(index, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
function getOptionLetter(option) {
|
|
||||||
const match = option.match(/OPTION_(\w)/);
|
|
||||||
return match ? match[1] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{question.IMAGE !== null && (
|
|
||||||
<div className='my-1'>
|
|
||||||
{/* <img src={question.IMAGE} alt="" /> */}
|
|
||||||
<h3>{question.IMAGE}</h3>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{question.AUDIO !== null && (
|
|
||||||
<div className='my-1'>
|
|
||||||
{/* <audio controls>
|
|
||||||
<source src={question.AUDIO} type="audio/mpeg" />
|
|
||||||
Your browser does not support the audio element.
|
|
||||||
</audio> */}
|
|
||||||
<h3>{question.AUDIO}</h3>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{question.VIDEO !== null && (
|
|
||||||
// <video src={question.VIDEO} controls className='my-1'></video>
|
|
||||||
<div className="my-1">
|
|
||||||
<h3>{question.VIDEO}</h3>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<p>{question.QUESTION}</p>
|
|
||||||
<div className="options">
|
|
||||||
{['OPTION_A', 'OPTION_B', 'OPTION_C', 'OPTION_D', 'OPTION_E'].map(
|
|
||||||
(optionKey) => {
|
|
||||||
if (options[optionKey]) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={optionKey}
|
|
||||||
className={`p-0 mb-3 form-check ${savedAnswer === getOptionLetter(optionKey) ? 'selected-answer' : ''}`}
|
|
||||||
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
onClick={() => handleChange(getOptionLetter(optionKey))}
|
|
||||||
>
|
|
||||||
<span className="option-label">{getOptionLetter(optionKey)}</span>
|
|
||||||
<span className="ms-2 option-text">{options[optionKey]}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MultipleChoiceQuestion;
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const TrueFalseQuestion = ({ question, onAnswer, savedAnswer, index }) => {
|
|
||||||
const handleChange = (value) => {
|
|
||||||
onAnswer(index, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{question.IMAGE !== null && (
|
|
||||||
<div className='my-1'>
|
|
||||||
{/* <img src={question.IMAGE} alt="" /> */}
|
|
||||||
<h3>{question.IMAGE}</h3>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{question.AUDIO !== null && (
|
|
||||||
<div className='my-1'>
|
|
||||||
{/* <audio controls>
|
|
||||||
<source src={question.AUDIO} type="audio/mpeg" />
|
|
||||||
Your browser does not support the audio element.
|
|
||||||
</audio> */}
|
|
||||||
<h3>{question.AUDIO}</h3>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{question.VIDEO !== null && (
|
|
||||||
// <video src={question.VIDEO} controls className='my-1'></video>
|
|
||||||
<div className="my-1">
|
|
||||||
<h3>{question.VIDEO}</h3>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<p>{question.QUESTION}</p>
|
|
||||||
<div className="options-tf">
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={`p-0 mb-3 form-check ${savedAnswer === '1' ? 'selected-answer' : ''}`}
|
|
||||||
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
onClick={() => handleChange('1')}
|
|
||||||
>
|
|
||||||
<span className="option-label">A</span>
|
|
||||||
<span className="ms-2 option-text">TRUE</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={`p-0 mb-3 form-check ${savedAnswer === '0' ? 'selected-answer' : ''}`}
|
|
||||||
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
onClick={() => handleChange('0')}
|
|
||||||
>
|
|
||||||
<span className="option-label">B</span>
|
|
||||||
<span className="ms-2 option-text">FALSE</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TrueFalseQuestion;
|
|
||||||
|
|
@ -1,248 +0,0 @@
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import exerciseService from '../services/exerciseService';
|
|
||||||
import { useSlugContext } from '../../../../utils/SlugContext';
|
|
||||||
import { unSlugify } from '../../../../utils/Constant';
|
|
||||||
|
|
||||||
import Popover from 'react-bootstrap/Popover';
|
|
||||||
|
|
||||||
import image from '../../../../assets/images/illustration/material-image.png';
|
|
||||||
import audio from '../../../../assets/audio/sample.mp3';
|
|
||||||
import video from '../../../../assets/video/sample.mp4';
|
|
||||||
|
|
||||||
const basicQuestions = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
question: "Which sentence is correct?",
|
|
||||||
options: ["She don't like apples.", "She doesn't like apples.", "She not likes apples.", "She don't likes apples."],
|
|
||||||
source: null,
|
|
||||||
correctAnswer: 1,
|
|
||||||
type: 'text'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
question: "Choose the correct form of the verb: 'He ___ to the store yesterday.'",
|
|
||||||
options: ["go", "went", "going", "goes"],
|
|
||||||
source: image,
|
|
||||||
correctAnswer: 1,
|
|
||||||
type: 'image'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
question: "Select the correct sentence:",
|
|
||||||
options: ["They was playing football.", "They were playing football.", "They playing football.", "They played football were."],
|
|
||||||
source: audio,
|
|
||||||
correctAnswer: 1,
|
|
||||||
type: 'audio'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
question: "Fill in the blank: 'I have ___ new car.'",
|
|
||||||
options: ["a", "an", "the", "some"],
|
|
||||||
source: video,
|
|
||||||
correctAnswer: 1,
|
|
||||||
type: 'video'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
question: "Which sentence is in the past perfect tense?",
|
|
||||||
options: ["I will have finished my homework.", "I had finished my homework.", "I finished my homework.", "I am finishing my homework."],
|
|
||||||
source: null,
|
|
||||||
correctAnswer: 1,
|
|
||||||
type: 'text'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
question: "Choose the correct pronoun: 'Neither of the boys lost ___ keys.'",
|
|
||||||
options: ["his", "their", "her", "its"],
|
|
||||||
source: null,
|
|
||||||
correctAnswer: 1,
|
|
||||||
type: 'text'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
question: "Identify the error in the sentence: 'She can sings very well.'",
|
|
||||||
options: ["sings", "can", "very", "well"],
|
|
||||||
source: null,
|
|
||||||
correctAnswer: 0,
|
|
||||||
type: 'text'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 8,
|
|
||||||
question: "Select the correct sentence:",
|
|
||||||
options: ["There is many apples on the table.", "There are many apples on the table.", "There are much apples on the table.", "There is much apples on the table."],
|
|
||||||
source: null,
|
|
||||||
correctAnswer: 1,
|
|
||||||
type: 'text'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 9,
|
|
||||||
question: "Choose the correct conditional form: 'If it rains, we ___ inside.'",
|
|
||||||
options: ["stay", "stays", "will stay", "stayed"],
|
|
||||||
source: null,
|
|
||||||
correctAnswer: 2,
|
|
||||||
type: 'text'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 10,
|
|
||||||
question: "Fill in the blank: 'She ___ to the gym every day.'",
|
|
||||||
options: ["when", "going", "gone", "goes"],
|
|
||||||
source: null,
|
|
||||||
correctAnswer: 3,
|
|
||||||
type: 'text'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export const useExercises = ( topic, level ) => {
|
|
||||||
const [questions, setQuestions] = useState([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [error, setError] = useState(null);
|
|
||||||
const [answers, setAnswers] = useState([]);
|
|
||||||
const [currentQuestion, setCurrentQuestion] = useState(0);
|
|
||||||
const { topicSlugMap } = useSlugContext();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const checkingData = async () => {
|
|
||||||
let levelId ='';
|
|
||||||
try {
|
|
||||||
levelId = await exerciseService.getLevelId(topicSlugMap[topic], unSlugify(level));
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to fetch questions:", error);
|
|
||||||
}finally{
|
|
||||||
const datas = await exerciseService.latestStdLearning(topicSlugMap[topic], levelId);
|
|
||||||
if (datas === null) {
|
|
||||||
const createLearning = await exerciseService.createStdLearning({ ID_LEVEL: levelId });
|
|
||||||
if (createLearning.data.statusCode === 201) {
|
|
||||||
fetchData();
|
|
||||||
}else{
|
|
||||||
checkingData();
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
fetchData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchData = async () => {
|
|
||||||
try {
|
|
||||||
const levelId = await exerciseService.getLevelId(topicSlugMap[topic], unSlugify(level));
|
|
||||||
const stdLearning = await exerciseService.latestStdLearning(topicSlugMap[topic], levelId);
|
|
||||||
if (stdLearning === null) {
|
|
||||||
await exerciseService.createStdLearning({ ID_LEVEL: levelId });
|
|
||||||
}
|
|
||||||
|
|
||||||
const datas = await exerciseService.fetchExercise(levelId);
|
|
||||||
console.log(datas);
|
|
||||||
|
|
||||||
// const data = allQuestions[topic]?.[level] || [];
|
|
||||||
const data = basicQuestions || [];
|
|
||||||
// setQuestions(data);
|
|
||||||
|
|
||||||
setQuestions(datas);
|
|
||||||
setAnswers(new Array(data.length).fill(null));
|
|
||||||
} catch (error) {
|
|
||||||
console.error("something wrong : ", error);
|
|
||||||
setError(error);
|
|
||||||
}finally{
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// checkingData();
|
|
||||||
fetchData();
|
|
||||||
}, [topic, level]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const savedAnswers = JSON.parse(localStorage.getItem('answers'));
|
|
||||||
if (savedAnswers) {
|
|
||||||
setAnswers(savedAnswers);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (answers.some(answer => answer !== null)) {
|
|
||||||
localStorage.setItem('answers', JSON.stringify(answers));
|
|
||||||
console.log(localStorage.getItem('answers'));
|
|
||||||
}
|
|
||||||
}, [answers]);
|
|
||||||
|
|
||||||
const popover = (
|
|
||||||
<Popover id="popover-basic">
|
|
||||||
<Popover.Header as="h4">Tips</Popover.Header>
|
|
||||||
<Popover.Body className='p-2'>
|
|
||||||
{/* Click the <strong>left arrow</strong> key to go to the previous question */}
|
|
||||||
{/* Click the <strong>right arrow</strong> key to go to the next question */}
|
|
||||||
<ul className='ps-3 m-0'>
|
|
||||||
<li>Click the <strong>left arrow</strong> key to go to the previous question</li>
|
|
||||||
<li>Click the <strong>right arrow</strong> key to go to the next question</li>
|
|
||||||
</ul>
|
|
||||||
</Popover.Body>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleAnswerSelect = (index) => {
|
|
||||||
const newAnswers = [...answers];
|
|
||||||
newAnswers[currentQuestion] = index;
|
|
||||||
setAnswers(newAnswers);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNextQuestion = () => {
|
|
||||||
setCurrentQuestion((prev) => (prev < questions.length - 1 ? prev + 1 : prev));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePrevQuestion = () => {
|
|
||||||
setCurrentQuestion((prev) => (prev > 0 ? prev - 1 : prev));
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleKeyDown = (event) => {
|
|
||||||
if (event.key === 'ArrowLeft') {
|
|
||||||
handlePrevQuestion();
|
|
||||||
} else if (event.key === 'ArrowRight') {
|
|
||||||
handleNextQuestion();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
|
||||||
};
|
|
||||||
}, [handlePrevQuestion, handleNextQuestion]);
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
const allAnswered = answers.every((answer) => answer !== null);
|
|
||||||
return allAnswered;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCurrentQuestionData = () => {
|
|
||||||
return questions[currentQuestion] || {};
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConfirmSubmit = () => {
|
|
||||||
setShowModal(false);
|
|
||||||
navigate(`/learning/module/${section}/${topic}/${level}/result`, {
|
|
||||||
state: { answers }
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
questions,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
answers,
|
|
||||||
currentQuestion,
|
|
||||||
popover,
|
|
||||||
setAnswers,
|
|
||||||
setCurrentQuestion,
|
|
||||||
handleAnswerSelect,
|
|
||||||
handleNextQuestion,
|
|
||||||
handlePrevQuestion,
|
|
||||||
handleSubmit,
|
|
||||||
getCurrentQuestionData,
|
|
||||||
handleConfirmSubmit
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useExercises;
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
|
|
||||||
const useMatchingPairs = () => {
|
|
||||||
const COLORS = ['#E9342D', '#FACC15', '#1FBC2F', '#0090FF', '#ED27D9'];
|
|
||||||
const [rightShuffled, setRightShuffled] = useState([]);
|
|
||||||
const [selectedLeft, setSelectedLeft] = useState(null);
|
|
||||||
const [selectedRight, setSelectedRight] = useState(null);
|
|
||||||
const [matchedPairs, setMatchedPairs] = useState([]);
|
|
||||||
const [colorMap, setColorMap] = useState({});
|
|
||||||
const leftItems = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'];
|
|
||||||
const rightItems = ['Item A', 'Item B', 'Item C', 'Item D', 'Item E'];
|
|
||||||
const [shuffle, setShuffle] = useState(true);
|
|
||||||
|
|
||||||
// Mengacak bagian kanan ketika komponen pertama kali dirender
|
|
||||||
useEffect(() => {
|
|
||||||
if (shuffle) {
|
|
||||||
shuffleRightItems();
|
|
||||||
}
|
|
||||||
}, [rightItems]);
|
|
||||||
|
|
||||||
// Fungsi untuk mengacak urutan bagian kanan
|
|
||||||
const shuffleRightItems = () => {
|
|
||||||
const shuffled = [...rightItems].sort(() => Math.random() - (leftItems.length/10));
|
|
||||||
setRightShuffled(shuffled);
|
|
||||||
setShuffle(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk menangani klik pada bagian kiri
|
|
||||||
const handleLeftChoose = (index) => {
|
|
||||||
setSelectedLeft(index);
|
|
||||||
|
|
||||||
if (selectedRight !== null) {
|
|
||||||
matchPair(index, selectedRight);
|
|
||||||
resetSelections();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk menangani klik pada bagian kanan
|
|
||||||
const handleRightChoose = (index) => {
|
|
||||||
setSelectedRight(index);
|
|
||||||
|
|
||||||
if (selectedLeft !== null) {
|
|
||||||
matchPair(selectedLeft, index);
|
|
||||||
resetSelections();
|
|
||||||
}
|
|
||||||
console.log(matchedPairs);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk mencocokkan pasangan
|
|
||||||
const matchPair = (leftIndex, rightIndex) => {
|
|
||||||
if (!matchedPairs[leftIndex]) {
|
|
||||||
const newMatchedPairs = [...matchedPairs];
|
|
||||||
newMatchedPairs[leftIndex] = rightShuffled[rightIndex];
|
|
||||||
|
|
||||||
const colorIndex = leftIndex % COLORS.length;
|
|
||||||
setColorMap((prevMap) => ({
|
|
||||||
...prevMap,
|
|
||||||
[leftIndex]: COLORS[colorIndex],
|
|
||||||
}));
|
|
||||||
|
|
||||||
setMatchedPairs(newMatchedPairs);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk mereset seleksi setelah pencocokan
|
|
||||||
const resetSelections = () => {
|
|
||||||
setSelectedLeft(null);
|
|
||||||
setSelectedRight(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetMatchPair = () => {
|
|
||||||
setMatchedPairs([]);
|
|
||||||
setColorMap({});
|
|
||||||
resetSelections();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
rightShuffled,
|
|
||||||
colorMap,
|
|
||||||
handleLeftChoose,
|
|
||||||
handleRightChoose,
|
|
||||||
matchPair,
|
|
||||||
resetSelections,
|
|
||||||
resetMatchPair
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useMatchingPairs;
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
import useExercises from './useExercises';
|
|
||||||
|
|
||||||
// correct answer
|
|
||||||
// 1. b
|
|
||||||
// 2. b
|
|
||||||
// 3. b
|
|
||||||
// 4. b
|
|
||||||
// 5. b
|
|
||||||
// 6. b
|
|
||||||
// 7. a
|
|
||||||
// 8. b
|
|
||||||
// 9. c
|
|
||||||
//10. d
|
|
||||||
|
|
||||||
const useResults = (answers) => {
|
|
||||||
const { level } = useParams();
|
|
||||||
const [score, setScore] = useState(0);
|
|
||||||
const [nextLevel, setNextLevel] = useState(1);
|
|
||||||
const [savedLevel, setSavedLevel] = useState(JSON.parse(localStorage.getItem('savedLevels')) || []);
|
|
||||||
const [savedScore, setSavedScore] = useState(JSON.parse(localStorage.getItem('savedScores')) || []);
|
|
||||||
|
|
||||||
const { questions } = useExercises();
|
|
||||||
|
|
||||||
const updateSavedScore = (scr) => {
|
|
||||||
const newSavedScore = [...savedScore];
|
|
||||||
const index = parseInt(level.slice(-1) - 1);
|
|
||||||
newSavedScore[index] = scr;
|
|
||||||
setSavedScore(newSavedScore);
|
|
||||||
localStorage.setItem('savedScores', JSON.stringify(newSavedScore));
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateSavedLevel = (index, newValue) => {
|
|
||||||
const newSavedLevel = [...savedLevel];
|
|
||||||
newSavedLevel[index] = newValue;
|
|
||||||
setSavedLevel(newSavedLevel);
|
|
||||||
localStorage.setItem('savedLevels', JSON.stringify(newSavedLevel));
|
|
||||||
};
|
|
||||||
|
|
||||||
const unlockSavedLevel = () => {
|
|
||||||
const newSavedLevel = savedLevel.map(() => false);
|
|
||||||
setSavedLevel(newSavedLevel);
|
|
||||||
localStorage.setItem('savedLevels', JSON.stringify(newSavedLevel));
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (answers && questions) {
|
|
||||||
let totalScore = 0;
|
|
||||||
|
|
||||||
answers.forEach((answer, index) => {
|
|
||||||
const question = questions[index];
|
|
||||||
if (question) {
|
|
||||||
if (answer === question.correctAnswer) {
|
|
||||||
totalScore += 100 / questions.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setScore(totalScore);
|
|
||||||
updateSavedScore(totalScore);
|
|
||||||
if (totalScore >= 90 && level === 'level-5') { //all done
|
|
||||||
setNextLevel(0);
|
|
||||||
unlockSavedLevel();
|
|
||||||
return;
|
|
||||||
} else if (totalScore >= 90 && level !== 'level-5') {
|
|
||||||
setNextLevel(5);
|
|
||||||
updateSavedLevel(4, false);
|
|
||||||
return;
|
|
||||||
} else if (totalScore >= 75) {
|
|
||||||
setNextLevel(4);
|
|
||||||
updateSavedLevel(3, false);
|
|
||||||
return;
|
|
||||||
} else if (totalScore >= 60) {
|
|
||||||
setNextLevel(3);
|
|
||||||
updateSavedLevel(2, false);
|
|
||||||
return;
|
|
||||||
} else if (totalScore >= 40) {
|
|
||||||
setNextLevel(2);
|
|
||||||
updateSavedLevel(1, false);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setNextLevel(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}, [answers, questions]);
|
|
||||||
|
|
||||||
localStorage.removeItem('answers');
|
|
||||||
return { score, nextLevel };
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useResults;
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
import axios from 'axios';
|
|
||||||
import { API_URL } from '../../../../utils/Constant';
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
headers: {
|
|
||||||
Authorization: localStorage.getItem('token')
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSoalNumber = (title) => {
|
|
||||||
const match = title.match(/\d+$/); // Mencari angka di akhir string
|
|
||||||
return match ? parseInt(match[0], 10) : 0; // Mengembalikan angka atau 0 jika tidak ditemukan
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk cek apakah std_learning sudah ada
|
|
||||||
const latestStdLearning = async (topic, level) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${API_URL}/stdLearning/level/${level}`, config);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
// console.error('Error checking std_learning:', error);
|
|
||||||
// throw error;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getLevelId = async (topicId, levelName) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${API_URL}/level/topic/${topicId}`, config);
|
|
||||||
const filteredData = response.data.data.levels.filter(item => item.NAME_LEVEL === levelName);
|
|
||||||
return filteredData[0].ID_LEVEL;
|
|
||||||
} catch (error) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Fungsi untuk cek apakah std_learning sudah ada
|
|
||||||
const checkStdLearning = async (token) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${API_URL}/std_learning`, config);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error checking std_learning:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk membuat std_learning
|
|
||||||
const createStdLearning = async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.post(`${API_URL}/stdLearning`, data, config);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error creating std_learning:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk mendapatkan data exercise
|
|
||||||
const fetchExercise = async (idLevel) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${API_URL}/exercise/level/${idLevel}`, config);
|
|
||||||
// return response.data;
|
|
||||||
|
|
||||||
const sortedData = response.data.payload.sort((a, b) => {
|
|
||||||
return getSoalNumber(a.TITLE) - getSoalNumber(b.TITLE);
|
|
||||||
});
|
|
||||||
|
|
||||||
return sortedData;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching exercise data:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
latestStdLearning,
|
|
||||||
getLevelId,
|
|
||||||
|
|
||||||
checkStdLearning,
|
|
||||||
createStdLearning,
|
|
||||||
fetchExercise
|
|
||||||
};
|
|
||||||
|
|
@ -1,331 +0,0 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { Container, Row, Col, ListGroup, Button, Modal, Alert, OverlayTrigger, Popover } from 'react-bootstrap';
|
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
|
||||||
import useExercises from '../hooks/useExercises';
|
|
||||||
import ilustration from '../../../../assets/images/illustration/submitExercise.png';
|
|
||||||
import Skeleton from 'react-loading-skeleton';
|
|
||||||
import MatchingPairs from './components/MatchingPair';
|
|
||||||
|
|
||||||
const Exercise = () => {
|
|
||||||
const [showModal, setShowModal] = useState(false);
|
|
||||||
const [showAlert, setShowAlert] = useState(false);
|
|
||||||
|
|
||||||
const [activeIndex, setActiveIndex] = useState(null);
|
|
||||||
|
|
||||||
const { section, topic, level } = useParams();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const {
|
|
||||||
questions,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
answers,
|
|
||||||
currentQuestion,
|
|
||||||
popover,
|
|
||||||
setAnswers,
|
|
||||||
setCurrentQuestion,
|
|
||||||
handleAnswerSelect,
|
|
||||||
handleNextQuestion,
|
|
||||||
handlePrevQuestion,
|
|
||||||
handleSubmit,
|
|
||||||
getCurrentQuestionData,
|
|
||||||
handleConfirmSubmit
|
|
||||||
} = useExercises( topic, level );
|
|
||||||
|
|
||||||
const handleSubmitWrapper = () => {
|
|
||||||
if (handleSubmit()) {
|
|
||||||
setShowModal(true);
|
|
||||||
} else {
|
|
||||||
setShowAlert(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// const handleConfirmSubmit = () => {
|
|
||||||
// setShowModal(false);
|
|
||||||
// navigate(`/learning/module/${section}/${topic}/${level}/result`, {
|
|
||||||
// state: { answers }
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
const handleCloseModal = () => setShowModal(false);
|
|
||||||
|
|
||||||
const currentQuestionData = getCurrentQuestionData();
|
|
||||||
const questionId = currentQuestionData.id || '';
|
|
||||||
|
|
||||||
|
|
||||||
const handleSelectLeft = (index) => {
|
|
||||||
setActiveIndex(index);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-2">
|
|
||||||
<Skeleton containerClassName='w-100' className='w-100 mb-1 rounded-3' count={6} style={{height:"4vh"}} />
|
|
||||||
</div>
|
|
||||||
<div className="col-10">
|
|
||||||
<Skeleton containerClassName='w-100' className='w-100 mb-1 rounded-3' style={{height:"5vh"}} />
|
|
||||||
<Skeleton containerClassName='w-100' className='w-50 mb-1 rounded-3' style={{height:"20vh"}} />
|
|
||||||
<Skeleton containerClassName='w-100' className='w-100 mb-1 rounded-3' style={{height:"30vh"}} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container fluid className='exercise-page'>
|
|
||||||
<Row>
|
|
||||||
<Col sm={2}>
|
|
||||||
<div className='p-3 rounded-4 bg-white'>
|
|
||||||
<div className="mb-3 d-flex justify-content-between align-items-center">
|
|
||||||
<h4 className='mb-0 text-gd fw-bold'>Pretest</h4>
|
|
||||||
<OverlayTrigger trigger="click" placement="right" overlay={popover}>
|
|
||||||
<i className=" bi bi-info-circle cursor-pointer text-secondary"></i>
|
|
||||||
</OverlayTrigger>
|
|
||||||
</div>
|
|
||||||
<ListGroup variant="flush" className='number-list'>
|
|
||||||
{questions.map((q, index) => (
|
|
||||||
<ListGroup.Item
|
|
||||||
key={q.ID_ADMIN_EXERCISE}
|
|
||||||
active={index === currentQuestion}
|
|
||||||
onClick={() => setCurrentQuestion(index)}
|
|
||||||
className={`border-0 rounded-3 number-label ${answers[index] !== null ? 'answered fw-bold' : ''}`}
|
|
||||||
style={{ cursor: 'pointer' }}
|
|
||||||
>
|
|
||||||
<i className={`me-2 bi bi-circle ${answers[index] !== null ? 'd-none' : 'd-block'}`}></i>
|
|
||||||
<i className={`me-2 bi bi-check2-circle ${answers[index] !== null ? 'd-block' : 'd-none'}`}></i>
|
|
||||||
{index+1}
|
|
||||||
</ListGroup.Item>
|
|
||||||
))}
|
|
||||||
</ListGroup>
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col sm={10}>
|
|
||||||
<div className='p-4 rounded-4 bg-white'>
|
|
||||||
<div className="pb-4 d-flex justify-content-between align-items-center">
|
|
||||||
<Button
|
|
||||||
variant='outline-blue'
|
|
||||||
className={`rounded-35 ${currentQuestion === 0 ? 'invisible' : 'visible'}`}
|
|
||||||
onClick={handlePrevQuestion}
|
|
||||||
disabled={currentQuestion === 0}
|
|
||||||
>
|
|
||||||
<i className="bi bi-arrow-left"></i>
|
|
||||||
</Button>
|
|
||||||
<h5 className='m-0'>{`Questions ${currentQuestion + 1} of ${questions.length}`}</h5>
|
|
||||||
<Button
|
|
||||||
variant="blue"
|
|
||||||
className={`rounded-35 ${currentQuestion === questions.length - 1 ? 'd-none' : ''}`}
|
|
||||||
onClick={handleNextQuestion}
|
|
||||||
disabled={currentQuestion === questions.length - 1}
|
|
||||||
>
|
|
||||||
Next Questions <i className="bi bi-arrow-right"></i>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="blue"
|
|
||||||
className={`rounded-35 px-4 ${currentQuestion === questions.length - 1 ? 'd-inline-block' : 'd-none'}`}
|
|
||||||
onClick={handleSubmitWrapper}
|
|
||||||
>
|
|
||||||
Submit <i className="bi bi-send"></i>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className='p-3 border rounded-3'>
|
|
||||||
{currentQuestionData.image && (
|
|
||||||
<div className="mb-2">
|
|
||||||
<img
|
|
||||||
src={currentQuestionData.image}
|
|
||||||
alt="question illustration"
|
|
||||||
className="img-fluid"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{currentQuestionData.IMAGE !== null && (
|
|
||||||
<div className='my-1'>
|
|
||||||
{/* <img src={currentQuestionData.source} alt="" /> */}
|
|
||||||
<h3>{currentQuestionData.IMAGE}</h3>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{currentQuestionData.AUDIO !== null && (
|
|
||||||
<div className='my-1'>
|
|
||||||
{/* <AudioPlayer audioSrc={currentQuestionData.source} /> */}
|
|
||||||
{/* <audio controls>
|
|
||||||
<source src={currentQuestionData.source} type="audio/mpeg" />
|
|
||||||
Your browser does not support the audio element.
|
|
||||||
</audio> */}
|
|
||||||
<h3>{currentQuestionData.AUDIO}</h3>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{currentQuestionData.VIDEO !== null && (
|
|
||||||
// <video src={currentQuestionData.source} controls className='my-1'></video>
|
|
||||||
<div className="my-1">
|
|
||||||
<h3>{currentQuestionData.VIDEO}</h3>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<p className='mb-4'>{currentQuestionData.QUESTION}</p>
|
|
||||||
{currentQuestionData.QUESTION_TYPE === "MCQ" && (
|
|
||||||
<div className="options">
|
|
||||||
{/* {currentQuestionData.options?.map((option, idx) => (
|
|
||||||
<div
|
|
||||||
key={idx}
|
|
||||||
className={`p-0 mb-3 form-check ${answers[currentQuestion] === idx ? 'selected-answer' : ''}`}
|
|
||||||
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
onClick={() => handleAnswerSelect(idx)}
|
|
||||||
>
|
|
||||||
<span className="option-label">{String.fromCharCode(65 + idx)}</span>
|
|
||||||
<span className="ms-2 option-text">{option}</span>
|
|
||||||
</div>
|
|
||||||
))} */}
|
|
||||||
<div
|
|
||||||
key={0}
|
|
||||||
className={`p-0 mb-3 form-check ${answers[currentQuestion] === 0 ? 'selected-answer' : ''}`}
|
|
||||||
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
onClick={() => handleAnswerSelect(0)}
|
|
||||||
>
|
|
||||||
<span className="option-label">A</span>
|
|
||||||
<span className="ms-2 option-text">{currentQuestionData.multipleChoices[0].OPTION_A}</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
key={1}
|
|
||||||
className={`p-0 mb-3 form-check ${answers[currentQuestion] === 1 ? 'selected-answer' : ''}`}
|
|
||||||
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
onClick={() => handleAnswerSelect(1)}
|
|
||||||
>
|
|
||||||
<span className="option-label">B</span>
|
|
||||||
<span className="ms-2 option-text">{currentQuestionData.multipleChoices[0].OPTION_B}</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
key={2}
|
|
||||||
className={`p-0 mb-3 form-check ${answers[currentQuestion] === 2 ? 'selected-answer' : ''}`}
|
|
||||||
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
onClick={() => handleAnswerSelect(2)}
|
|
||||||
>
|
|
||||||
<span className="option-label">C</span>
|
|
||||||
<span className="ms-2 option-text">{currentQuestionData.multipleChoices[0].OPTION_C}</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
key={3}
|
|
||||||
className={`p-0 mb-3 form-check ${answers[currentQuestion] === 3 ? 'selected-answer' : ''}`}
|
|
||||||
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
onClick={() => handleAnswerSelect(3)}
|
|
||||||
>
|
|
||||||
<span className="option-label">D</span>
|
|
||||||
<span className="ms-2 option-text">{currentQuestionData.multipleChoices[0].OPTION_D}</span>
|
|
||||||
</div>
|
|
||||||
{currentQuestionData.multipleChoices[0].OPTION_E && (
|
|
||||||
<div
|
|
||||||
key={4}
|
|
||||||
className={`p-0 mb-3 form-check ${answers[currentQuestion] === 4 ? 'selected-answer' : ''}`}
|
|
||||||
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
onClick={() => handleAnswerSelect(4)}
|
|
||||||
>
|
|
||||||
<span className="option-label">E</span>
|
|
||||||
<span className="ms-2 option-text">{currentQuestionData.multipleChoices[0].OPTION_E}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{currentQuestionData.QUESTION_TYPE === "TFQ" && (
|
|
||||||
<div className="options-tf">
|
|
||||||
<div
|
|
||||||
key={0}
|
|
||||||
className={`p-0 mb-3 form-check ${answers[currentQuestion] === 0 ? 'selected-answer' : ''}`}
|
|
||||||
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
onClick={() => handleAnswerSelect(0)}
|
|
||||||
>
|
|
||||||
<span className="option-label">A</span>
|
|
||||||
<span className="ms-2 option-text">TRUE</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
key={1}
|
|
||||||
className={`p-0 mb-3 form-check ${answers[currentQuestion] === 1 ? 'selected-answer' : ''}`}
|
|
||||||
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
onClick={() => handleAnswerSelect(1)}
|
|
||||||
>
|
|
||||||
<span className="option-label">B</span>
|
|
||||||
<span className="ms-2 option-text">FALSE</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{currentQuestionData.QUESTION_TYPE === "MPQ" &&(
|
|
||||||
// <div className="w-100 options-mp d-flex justify-content-between">
|
|
||||||
// <div>
|
|
||||||
// {currentQuestionData.matchingPairs?.map((option, idx) => (
|
|
||||||
// <div
|
|
||||||
// key={idx}
|
|
||||||
// className={`p-0 mb-3 form-check ${answers[currentQuestion] === idx ? 'selected-answer' : ''}`}
|
|
||||||
// style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
// onClick={() => handleLeftChoose(idx)}
|
|
||||||
// >
|
|
||||||
// <span className={`option-label left label-${idx + 1}${activeIndex === idx ? " active" : ""}`}>{idx + 1}</span>
|
|
||||||
// {/* <span className="option-label">{idx + 1}</span> */}
|
|
||||||
// {/* <span className="ms-2 option-text">{option.LEFT_PAIR}</span> */}
|
|
||||||
// <span className={`ms-2 option-text left label-${idx + 1}${activeIndex === idx ? " active" : ""}`}>{option.LEFT_PAIR}</span>
|
|
||||||
// </div>
|
|
||||||
// ))}
|
|
||||||
// </div>
|
|
||||||
// <div>
|
|
||||||
// {currentQuestionData.matchingPairs?.map((option, idx) => (
|
|
||||||
// <div
|
|
||||||
// key={idx}
|
|
||||||
// className={`p-0 mb-3 form-check ${answers[currentQuestion] === idx ? 'selected-answer' : ''}`}
|
|
||||||
// style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
|
|
||||||
// onClick={() => handleRightChoose(idx)}
|
|
||||||
// >
|
|
||||||
// <span className={`option-label label-${idx + 1}${activeIndex === idx ? " active" : ""}`}>{idx + 1}</span>
|
|
||||||
// {/* <span className="option-label">{idx + 1}</span> */}
|
|
||||||
// {/* <span className="ms-2 option-text">{option.LEFT_PAIR}</span> */}
|
|
||||||
// <span className={`ms-2 option-text label-${idx + 1}${activeIndex === idx ? " active" : ""}`}>{option.RIGHT_PAIR}</span>
|
|
||||||
// </div>
|
|
||||||
// ))}
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
<MatchingPairs pairData={currentQuestionData.matchingPairs} numberQuestion={currentQuestion} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Alert show={showAlert} variant="danger" className="custom-alert" onClose={() => setShowAlert(false)} dismissible>
|
|
||||||
<Alert.Heading>ATTENTION!</Alert.Heading>
|
|
||||||
<span>Please answer all questions before submitting.</span>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
{/* <Modal show={showModal} onHide={handleCloseModal} centered>
|
|
||||||
<Modal.Header closeButton>
|
|
||||||
<Modal.Title>Confirmation</Modal.Title>
|
|
||||||
</Modal.Header>
|
|
||||||
<Modal.Body>
|
|
||||||
<p>Are you sure you want to submit your answer?</p>
|
|
||||||
</Modal.Body>
|
|
||||||
<Modal.Footer>
|
|
||||||
<Button variant="secondary" onClick={handleCloseModal}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button variant="primary" onClick={handleConfirmSubmit}>
|
|
||||||
Yes, Submit <i className="bi bi-send"></i>
|
|
||||||
</Button>
|
|
||||||
</Modal.Footer>
|
|
||||||
</Modal> */}
|
|
||||||
|
|
||||||
<Modal show={showModal} onHide={handleCloseModal} centered>
|
|
||||||
<Modal.Body className='p-4 d-flex flex-column items-center'>
|
|
||||||
<h4 className='mb-4 fw-bold text-dark'>Proceed with <span className='text-blue'>Submission</span>?</h4>
|
|
||||||
<img src={ilustration} alt="" />
|
|
||||||
<p className='my-3 text-muted fw-light'>Confirm submission? There's no going back.</p>
|
|
||||||
<div className="mt-4 w-100 d-flex justify-content-center">
|
|
||||||
<Button variant="outline-blue" className="w-50 py-2 px-5 mx-1 rounded-35" onClick={handleCloseModal}>Check Again</Button>
|
|
||||||
<Button variant="gd" className="w-50 py-2 px-5 mx-1 rounded-35" onClick={handleConfirmSubmit}>Confirm</Button>
|
|
||||||
</div>
|
|
||||||
</Modal.Body>
|
|
||||||
</Modal>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Exercise;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
// import React from 'react';
|
|
||||||
// import { Link, useParams, useLocation } from 'react-router-dom';
|
|
||||||
// import { Container, Row, Col, Button } from 'react-bootstrap';
|
|
||||||
// import staydown from '../../assets/images/user/resultStay.png'
|
|
||||||
// import jump from '../../assets/images/user/resultJump.png'
|
|
||||||
// import finish from '../../assets/images/user/resultFinish.png'
|
|
||||||
|
|
||||||
// const ExerciseResult = () => {
|
|
||||||
// const { section, topic } = useParams();
|
|
||||||
// const location = useLocation();
|
|
||||||
// localStorage.removeItem('answers');
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <Container className="result-page">
|
|
||||||
// <Row>
|
|
||||||
|
|
||||||
// {/* nilai sudah diatas KKM */}
|
|
||||||
// <Col xs={12} className='p-4 bg-white rounded-4 d-flex flex-column align-items-center'>
|
|
||||||
// <h4>Your Result!</h4>
|
|
||||||
// <img src={jump} alt="/" className='h-50'/>
|
|
||||||
// <p className='my-3 text-muted'>
|
|
||||||
// You scored <span className='text-blue fw-bold'> 60/100</span>. Great job! You can jump to...
|
|
||||||
// </p>
|
|
||||||
// <h1 className='mb-3 display-5 text-blue fw-bold'>LEVEL 3</h1>
|
|
||||||
// <Button as={Link} to={`/learning/${section}/${topic}`} variant='warning' className='py-2 px-5 rounded-35'>Continue</Button>
|
|
||||||
// </Col>
|
|
||||||
|
|
||||||
// {/* jika nilai dibawah KKM */}
|
|
||||||
// <Col xs={12} className='p-4 bg-white rounded-4 d-flex flex-column align-items-center'>
|
|
||||||
// <h4>Your Result!</h4>
|
|
||||||
// <img src={staydown} alt="/" className='h-50'/>
|
|
||||||
// <p className='my-3 text-muted'>
|
|
||||||
// You scored <span className='text-danger fw-bold'> 30/100</span>. Good effort! To improve, you can focus on...
|
|
||||||
// </p>
|
|
||||||
// <h1 className='mb-3 display-5 text-danger fw-bold'>LEVEL 3</h1>
|
|
||||||
// <Button as={Link} to={`/learning/${section}/${topic}`} variant='warning' className='py-2 px-5 rounded-35'>Continue</Button>
|
|
||||||
// </Col>
|
|
||||||
|
|
||||||
// {/* jika nilai sudah bagus tetapi belum cukup untuk ke level selanjutnya */}
|
|
||||||
// <Col xs={12} className='p-4 bg-white rounded-4 d-flex flex-column align-items-center'>
|
|
||||||
// <h4>Your Result!</h4>
|
|
||||||
// <img src={staydown} alt="/" className='h-50'/>
|
|
||||||
// <p className='my-3 text-muted text-center'>
|
|
||||||
// You scored <span className='text-blue fw-bold'> 30/100</span>. Learning is a journey. <br />
|
|
||||||
// Let's explore this level further to deepen your knowledge.
|
|
||||||
// </p>
|
|
||||||
// <h1 className='mb-3 display-5 fw-bold'>LEVEL 2</h1>
|
|
||||||
// <Button as={Link} to={`/learning/${section}/${topic}`} variant='warning' className='py-2 px-5 rounded-35'>Continue</Button>
|
|
||||||
// </Col>
|
|
||||||
|
|
||||||
// {/* jika yang telah dikerjakan adalah level 5 */}
|
|
||||||
// <Col xs={12} className='p-4 bg-white rounded-4 d-flex flex-column align-items-center'>
|
|
||||||
// <h4>Your Result!</h4>
|
|
||||||
// <img src={finish} alt="/" className='h-50'/>
|
|
||||||
// <p className='my-3 text-muted'>
|
|
||||||
// Way to go! You conquered <span className='text-blue fw-bold'> LEVEL 5</span> with a <span className='text-blue fw-bold'> 90/100</span>! You're a rock star!
|
|
||||||
// </p>
|
|
||||||
// <h1 className='mb-3 display-5 fw-bold'>LEVEL 2</h1>
|
|
||||||
// <Button as={Link} to={`/learning/${section}/${topic}`} variant='warning' className='py-2 px-5 rounded-35'>Continue</Button>
|
|
||||||
// </Col>
|
|
||||||
// </Row>
|
|
||||||
// </Container>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export default ExerciseResult;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { Link, useParams, useLocation } from 'react-router-dom';
|
|
||||||
import { Container, Row, Col, Button } from 'react-bootstrap';
|
|
||||||
import staydown from '../../../../assets/images/illustration/resultStay.png';
|
|
||||||
import jump from '../../../../assets/images/illustration/resultJump.png';
|
|
||||||
import finish from '../../../../assets/images/illustration/resultFinish.png';
|
|
||||||
import useResults from '../hooks/useResults';
|
|
||||||
|
|
||||||
const ExerciseResult = () => {
|
|
||||||
const { section, topic, level } = useParams();
|
|
||||||
const location = useLocation();
|
|
||||||
const { answers } = location.state || { answers: [] };
|
|
||||||
|
|
||||||
const { score, nextLevel } = useResults(answers);
|
|
||||||
|
|
||||||
let resultImage;
|
|
||||||
let resultText;
|
|
||||||
let resultColor;
|
|
||||||
|
|
||||||
if (nextLevel === 0) {
|
|
||||||
resultImage = finish;
|
|
||||||
resultColor = 'text-blue';
|
|
||||||
resultText = (
|
|
||||||
<> Way to go! You conquered <span className='text-blue fw-bold'>LEVEL 5</span> with a <span className='text-blue fw-bold'>{score}/100</span>! You're a rock star!</>
|
|
||||||
);
|
|
||||||
} else if (score >= 60) {
|
|
||||||
resultImage = jump;
|
|
||||||
resultColor = 'text-blue';
|
|
||||||
resultText = (
|
|
||||||
<> You scored <span className='text-blue fw-bold'>{score}/100</span>. Great job! You can jump to...</>
|
|
||||||
);
|
|
||||||
} else if (score >= 40) {
|
|
||||||
resultImage = staydown;
|
|
||||||
resultColor = '';
|
|
||||||
resultText = (
|
|
||||||
<> You scored <span className='text-blue fw-bold'>{score}/100</span>. Learning is a journey. <br/> Let's explore this level further to deepen your knowledge.</>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
resultImage = staydown;
|
|
||||||
resultColor = 'text-red';
|
|
||||||
resultText = (
|
|
||||||
<> You scored <span className='text-red fw-bold'>{score}/100</span>. Good effort! To improve, you can focus on...</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container className="result-page">
|
|
||||||
<Row>
|
|
||||||
<Col xs={12} className='p-4 bg-white rounded-4 d-flex flex-column align-items-center'>
|
|
||||||
<h4>Your Result!</h4>
|
|
||||||
<img src={resultImage} alt="/" className='h-50'/>
|
|
||||||
<p className='my-3 text-muted text-center'>
|
|
||||||
{resultText}
|
|
||||||
</p>
|
|
||||||
<h1 className={`mb-3 display-5 fw-bold ${resultColor}`}>{nextLevel === 0 ? '' : `LEVEL ${nextLevel}`}</h1>
|
|
||||||
<Button as={Link} to={`/learning/${section}/${topic}`} variant='warning' className='py-2 px-5 rounded-35'>Continue</Button>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ExerciseResult;
|
|
||||||
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
|
|
||||||
const COLORS = ['#E9342D', '#FACC15', '#1FBC2F', '#0090FF', '#ED27D9'];
|
|
||||||
|
|
||||||
const MatchingPairs = ( stateData ) => {
|
|
||||||
const [selectedLeft, setSelectedLeft] = useState(null);
|
|
||||||
const [selectedRight, setSelectedRight] = useState(null);
|
|
||||||
const [matchedPairs, setMatchedPairs] = useState([]);
|
|
||||||
const [colorMap, setColorMap] = useState({});
|
|
||||||
const [leftItems, setLeftPairs] = useState([]);
|
|
||||||
const [rightItems, setRightPairs] = useState([]);
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(stateData);
|
|
||||||
const pairItem = stateData.pairData;
|
|
||||||
const left = pairItem.map(pair => pair.LEFT_PAIR);
|
|
||||||
const right = pairItem.map(pair => pair.RIGHT_PAIR);
|
|
||||||
const shuffledRight = [...right].sort(() => Math.random() - (left.length/10));
|
|
||||||
|
|
||||||
setLeftPairs(left);
|
|
||||||
setRightPairs(shuffledRight);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
// Fungsi untuk menangani klik pada bagian kiri
|
|
||||||
const handleLeftClick = (index) => {
|
|
||||||
setSelectedLeft(index);
|
|
||||||
|
|
||||||
if (selectedRight !== null) {
|
|
||||||
matchPair(index, selectedRight);
|
|
||||||
resetSelections();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk menangani klik pada bagian kanan
|
|
||||||
const handleRightClick = (index) => {
|
|
||||||
setSelectedRight(index);
|
|
||||||
|
|
||||||
if (selectedLeft !== null) {
|
|
||||||
matchPair(selectedLeft, index);
|
|
||||||
resetSelections();
|
|
||||||
}
|
|
||||||
console.log(selectedRight);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk mencocokkan pasangan
|
|
||||||
const matchPair = (leftIndex, rightIndex) => {
|
|
||||||
if (!matchedPairs[leftIndex]) {
|
|
||||||
const newMatchedPairs = [...matchedPairs];
|
|
||||||
newMatchedPairs[leftIndex] = rightItems[rightIndex];
|
|
||||||
|
|
||||||
const colorIndex = leftIndex % COLORS.length;
|
|
||||||
setColorMap((prevMap) => ({
|
|
||||||
...prevMap,
|
|
||||||
[leftIndex]: COLORS[colorIndex],
|
|
||||||
}));
|
|
||||||
|
|
||||||
setMatchedPairs(newMatchedPairs);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk mereset seleksi setelah pencocokan
|
|
||||||
const resetSelections = () => {
|
|
||||||
setSelectedLeft(null);
|
|
||||||
setSelectedRight(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetAnswers = () => {
|
|
||||||
setMatchedPairs([]);
|
|
||||||
setColorMap({});
|
|
||||||
resetSelections();
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAllMatched = () => {
|
|
||||||
console.log('gatau');
|
|
||||||
return matchedPairs.length === leftItems.length && matchedPairs.every((pair) => pair !== undefined && pair !== null);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="gatau">
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '50px' }}>
|
|
||||||
{/* Bagian Kiri */}
|
|
||||||
<div>
|
|
||||||
<h3>Left Items</h3>
|
|
||||||
<ul style={{ listStyle: 'none', padding: 0 }}>
|
|
||||||
{leftItems.map((item, index) => (
|
|
||||||
<li
|
|
||||||
key={index}
|
|
||||||
onClick={() => handleLeftClick(index)}
|
|
||||||
style={{
|
|
||||||
cursor: 'pointer',
|
|
||||||
backgroundColor: colorMap[index] || 'transparent',
|
|
||||||
padding: '10px',
|
|
||||||
marginBottom: '5px',
|
|
||||||
border: selectedLeft === index ? '2px solid black' : '2px solid #fff',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Bagian Kanan */}
|
|
||||||
<div>
|
|
||||||
<h3>Right Items (Shuffled)</h3>
|
|
||||||
<ul style={{ listStyle: 'none', padding: 0 }}>
|
|
||||||
{rightItems.map((item, index) => (
|
|
||||||
<li
|
|
||||||
key={index}
|
|
||||||
onClick={() => handleRightClick(index)}
|
|
||||||
style={{
|
|
||||||
cursor: 'pointer',
|
|
||||||
backgroundColor: matchedPairs.includes(item) ? colorMap[matchedPairs.indexOf(item)] : 'transparent',
|
|
||||||
padding: '10px',
|
|
||||||
marginBottom: '5px',
|
|
||||||
border: selectedRight === index ? '2px solid black' : '2px solid #fff',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* <div style={{ marginTop: '20px' }}>
|
|
||||||
{isAllMatched() ? (
|
|
||||||
<div style={{ color: 'green' }}>Semua pasangan sudah terjawab!</div>
|
|
||||||
) : (
|
|
||||||
<div style={{ color: 'red' }}>Belum semua pasangan terjawab.</div>
|
|
||||||
)}
|
|
||||||
</div> */}
|
|
||||||
<button onClick={resetAnswers}>reset</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MatchingPairs;
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import exerciseService from '../services/exerciseServices';
|
|
||||||
import { useSlugContext } from '../../../../utils/SlugContext';
|
|
||||||
import { unSlugify } from '../../../../utils/Constant';
|
|
||||||
|
|
||||||
export const useExercise = (topic, level) => {
|
|
||||||
const [questions, setQuestions] = useState([]);
|
|
||||||
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
|
||||||
const [answers, setAnswers] = useState({});
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
|
||||||
const [error, setError] = useState(null);
|
|
||||||
const [levelId, setLevelId] = useState(null);
|
|
||||||
const [learningId, setLearningId] = useState(null);
|
|
||||||
const { topicSlugMap } = useSlugContext();
|
|
||||||
|
|
||||||
const fetchData = async () => {
|
|
||||||
try {
|
|
||||||
const getLevelId = await exerciseService.getLevelId(topicSlugMap[topic], unSlugify(level));
|
|
||||||
setLevelId(getLevelId);
|
|
||||||
const stdLearning = await exerciseService.checkStdLearning(getLevelId);
|
|
||||||
if (stdLearning === null) {
|
|
||||||
const newLearningId = await exerciseService.createStdLearning({ ID_LEVEL: getLevelId });
|
|
||||||
setLearningId(newLearningId);
|
|
||||||
}else{
|
|
||||||
setLearningId(stdLearning);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const data = await exerciseService.fetchExercise(getLevelId);
|
|
||||||
setQuestions(data);
|
|
||||||
if (localStorage.getItem(getLevelId)) {
|
|
||||||
const savedAnswers = JSON.parse(localStorage.getItem(getLevelId));
|
|
||||||
setAnswers(savedAnswers);
|
|
||||||
}else{
|
|
||||||
setAnswers(new Array(data.length).fill(null));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("something wrong : ", error);
|
|
||||||
setError(error);
|
|
||||||
}finally{
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fetch data soal dari API saat komponen dimount
|
|
||||||
useEffect(() => {
|
|
||||||
fetchData();
|
|
||||||
}, [topic, level]);
|
|
||||||
|
|
||||||
// Fungsi untuk menangani jawaban user dan menyimpannya ke localStorage
|
|
||||||
const handleAnswer = (index, answer) => {
|
|
||||||
// const newAnswers = { ...answers, [index]: answer };
|
|
||||||
const newAnswers = [...answers];
|
|
||||||
newAnswers[index] = answer;
|
|
||||||
setAnswers(newAnswers);
|
|
||||||
console.log(newAnswers);
|
|
||||||
localStorage.setItem(levelId, JSON.stringify(newAnswers));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk menyimpan pasangan jawaban Matching Pair ketika sudah lengkap
|
|
||||||
const handleMatchingPairs = (questionId, pairs) => {
|
|
||||||
const isComplete = pairs.every(pair => pair.left && pair.right); // Pastikan semua pasangan sudah lengkap
|
|
||||||
if (isComplete) {
|
|
||||||
handleAnswer(questionId, pairs); // Simpan jawaban jika sudah lengkap
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk pindah ke soal selanjutnya
|
|
||||||
const nextQuestion = () => {
|
|
||||||
if (currentQuestionIndex < questions.length - 1) {
|
|
||||||
setCurrentQuestionIndex(currentQuestionIndex + 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk kembali ke soal sebelumnya
|
|
||||||
const prevQuestion = () => {
|
|
||||||
if (currentQuestionIndex > 0) {
|
|
||||||
setCurrentQuestionIndex(currentQuestionIndex - 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
questions,
|
|
||||||
currentQuestion: questions[currentQuestionIndex],
|
|
||||||
currentQuestionIndex,
|
|
||||||
setCurrentQuestionIndex,
|
|
||||||
answers,
|
|
||||||
isLoading,
|
|
||||||
error,
|
|
||||||
handleAnswer,
|
|
||||||
handleMatchingPairs,
|
|
||||||
nextQuestion,
|
|
||||||
prevQuestion,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
import axios from 'axios';
|
|
||||||
import { API_URL } from '../../../../utils/Constant';
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
headers: {
|
|
||||||
Authorization: localStorage.getItem('token')
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSoalNumber = (title) => {
|
|
||||||
const match = title.match(/\d+$/); // Mencari angka di akhir string
|
|
||||||
return match ? parseInt(match[0], 10) : 0; // Mengembalikan angka atau 0 jika tidak ditemukan
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk cek apakah std_learning sudah ada
|
|
||||||
const checkStdLearning = async (level) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${API_URL}/stdLearning/level/${level}`, config);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
// console.error('Error checking std_learning:', error);
|
|
||||||
// throw error;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getLevelId = async (topicId, levelName) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${API_URL}/level/topic/${topicId}`, config);
|
|
||||||
const filteredData = response.data.data.levels.filter(item => item.NAME_LEVEL === levelName);
|
|
||||||
return filteredData[0].ID_LEVEL;
|
|
||||||
} catch (error) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk membuat std_learning
|
|
||||||
const createStdLearning = async (data) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.post(`${API_URL}/stdLearning`, data, config);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error creating std_learning:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fungsi untuk mendapatkan data exercise
|
|
||||||
const fetchExercise = async (idLevel) => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`${API_URL}/exercise/level/${idLevel}`, config);
|
|
||||||
// return response.data;
|
|
||||||
|
|
||||||
const sortedData = response.data.payload.sort((a, b) => {
|
|
||||||
return getSoalNumber(a.TITLE) - getSoalNumber(b.TITLE);
|
|
||||||
});
|
|
||||||
|
|
||||||
return sortedData;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching exercise data:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getLevelId,
|
|
||||||
checkStdLearning,
|
|
||||||
createStdLearning,
|
|
||||||
fetchExercise
|
|
||||||
};
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { useExercise } from '../hooks/useExercise';
|
|
||||||
import MultipleChoiceQuestion from './MultipleChoiceQuestion';
|
|
||||||
import TrueFalseQuestion from './TrueFalseQuestion';
|
|
||||||
import MatchingPairsQuestion from './MatchingPairsQuestion';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
const Exercise = () => {
|
|
||||||
const { topic, level } = useParams();
|
|
||||||
const {
|
|
||||||
questions,
|
|
||||||
currentQuestion,
|
|
||||||
currentQuestionIndex,
|
|
||||||
setCurrentQuestionIndex,
|
|
||||||
answers,
|
|
||||||
isLoading,
|
|
||||||
error,
|
|
||||||
handleAnswer,
|
|
||||||
nextQuestion,
|
|
||||||
prevQuestion,
|
|
||||||
} = useExercise(topic, level);
|
|
||||||
|
|
||||||
if (isLoading) return <p>Loading...</p>;
|
|
||||||
if (error) return <p>Error loading questions.</p>;
|
|
||||||
|
|
||||||
const renderQuestion = () => {
|
|
||||||
switch (currentQuestion.QUESTION_TYPE) {
|
|
||||||
case 'MCQ':
|
|
||||||
return (
|
|
||||||
<MultipleChoiceQuestion
|
|
||||||
question={currentQuestion}
|
|
||||||
onAnswer={handleAnswer}
|
|
||||||
savedAnswer={answers[currentQuestionIndex]}
|
|
||||||
index={currentQuestionIndex}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case 'TFQ':
|
|
||||||
return (
|
|
||||||
<TrueFalseQuestion
|
|
||||||
question={currentQuestion}
|
|
||||||
onAnswer={handleAnswer}
|
|
||||||
savedAnswer={answers[currentQuestionIndex]}
|
|
||||||
index={currentQuestionIndex}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case 'MPQ':
|
|
||||||
return (
|
|
||||||
<MatchingPairsQuestion
|
|
||||||
question={currentQuestion}
|
|
||||||
onAnswer={handleAnswer}
|
|
||||||
savedAnswer={answers[currentQuestionIndex]}
|
|
||||||
index={currentQuestionIndex}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return <p>Unknown question type.</p>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h2>Exercise</h2>
|
|
||||||
<div>
|
|
||||||
{/* List nomor soal */}
|
|
||||||
<div>
|
|
||||||
{questions.map((q, index) => (
|
|
||||||
<button
|
|
||||||
key={q.ID_ADMIN_EXERCISE}
|
|
||||||
onClick={() => setCurrentQuestionIndex(index)}
|
|
||||||
style={{
|
|
||||||
fontWeight: index === currentQuestionIndex ? 'bold' : 'normal',
|
|
||||||
color: answers[index] !== null ? 'red' : '#000',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{index + 1}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
{/* Soal saat ini */}
|
|
||||||
<div>{renderQuestion()}</div>
|
|
||||||
{/* Navigasi Next dan Previous */}
|
|
||||||
<div>
|
|
||||||
<button onClick={prevQuestion} disabled={currentQuestionIndex === 0}>
|
|
||||||
Previous
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={nextQuestion}
|
|
||||||
disabled={currentQuestionIndex === questions.length - 1}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Exercise;
|
|
||||||
|
|
@ -1,222 +0,0 @@
|
||||||
// import React, { useState, useEffect } from 'react';
|
|
||||||
|
|
||||||
// const MatchingPairsQuestion = ({ question, onAnswer, savedAnswer, index }) => {
|
|
||||||
// const [pairs, setPairs] = useState([]);
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// const initialPairs = question.matchingPairs.map((pair) => ({
|
|
||||||
// left: pair.LEFT_PAIR,
|
|
||||||
// right: '',
|
|
||||||
// }));
|
|
||||||
// if (savedAnswer) {
|
|
||||||
// setPairs(savedAnswer);
|
|
||||||
// } else {
|
|
||||||
// setPairs(initialPairs);
|
|
||||||
// }
|
|
||||||
// }, [question, savedAnswer]);
|
|
||||||
|
|
||||||
// const rightOptions = question.matchingPairs.map((pair) => pair.RIGHT_PAIR);
|
|
||||||
|
|
||||||
// const handleSelect = (leftIndex, value) => {
|
|
||||||
// const newPairs = [...pairs];
|
|
||||||
// newPairs[leftIndex].right = value;
|
|
||||||
// setPairs(newPairs);
|
|
||||||
|
|
||||||
// const isComplete = newPairs.every((pair) => pair.right !== '');
|
|
||||||
// if (isComplete) {
|
|
||||||
// onAnswer(index , newPairs);
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <div>
|
|
||||||
// <h3>{question.TITLE}</h3>
|
|
||||||
// <p>{question.QUESTION}</p>
|
|
||||||
// {question.VIDEO && (
|
|
||||||
// <video controls>
|
|
||||||
// <source src={question.VIDEO} type="video/mp4" />
|
|
||||||
// </video>
|
|
||||||
// )}
|
|
||||||
// {question.IMAGE && <img src={question.IMAGE} alt="Question" />}
|
|
||||||
// {question.AUDIO && <audio controls src={question.AUDIO}></audio>}
|
|
||||||
// <div>
|
|
||||||
// {pairs.map((pair, index) => (
|
|
||||||
// <div key={index} style={{ display: 'flex', marginBottom: '10px' }}>
|
|
||||||
// <div style={{ marginRight: '20px' }}>{pair.left}</div>
|
|
||||||
// <select
|
|
||||||
// value={pair.right}
|
|
||||||
// onChange={(e) => handleSelect(index, e.target.value)}
|
|
||||||
// >
|
|
||||||
// <option value="">Pilih pasangan</option>
|
|
||||||
// {rightOptions.map((option, idx) => (
|
|
||||||
// <option key={idx} value={option}>
|
|
||||||
// {option}
|
|
||||||
// </option>
|
|
||||||
// ))}
|
|
||||||
// </select>
|
|
||||||
// </div>
|
|
||||||
// ))}
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export default MatchingPairsQuestion;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// components/MatchingPairsQuestion.js
|
|
||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
|
|
||||||
const colors = ['#E9342D', '#FACC15', '#1FBC2F', '#0090FF', '#ED27D9'];
|
|
||||||
|
|
||||||
const shuffleArray = (array) => {
|
|
||||||
return array
|
|
||||||
.map((value) => ({ value, sort: Math.random() }))
|
|
||||||
.sort((a, b) => a.sort - b.sort)
|
|
||||||
.map(({ value }) => value);
|
|
||||||
};
|
|
||||||
|
|
||||||
function arrayToString(arr) {
|
|
||||||
return arr.join(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringToArray(str) {
|
|
||||||
return str.split(', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
const MatchingPairsQuestion = ({ question, onAnswer, savedAnswer, index }) => {
|
|
||||||
const [pairs, setPairs] = useState([]);
|
|
||||||
const [selectedLeft, setSelectedLeft] = useState(null);
|
|
||||||
const [selectedRight, setSelectedRight] = useState(null);
|
|
||||||
const [rightOptions, setRightOptions] = useState([]);
|
|
||||||
const [isComplete, setIsComplete] = useState(false);
|
|
||||||
const [isShuffled, setIsShuffled] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const initialPairs = question.matchingPairs.map((pair) => ({
|
|
||||||
left: pair.LEFT_PAIR,
|
|
||||||
right: '',
|
|
||||||
color: colors[question.matchingPairs.indexOf(pair) % colors.length],
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (savedAnswer) {
|
|
||||||
const arrSavedAnswer = stringToArray(savedAnswer);
|
|
||||||
const updatedPairs = initialPairs.map((pair, index) => ({
|
|
||||||
...pair,
|
|
||||||
right: arrSavedAnswer[index],
|
|
||||||
}));
|
|
||||||
setPairs(updatedPairs);
|
|
||||||
} else {
|
|
||||||
setPairs(initialPairs);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isShuffled) {
|
|
||||||
setIsShuffled(true);
|
|
||||||
setRightOptions(shuffleArray(question.matchingPairs.map((pair) => pair.RIGHT_PAIR)));
|
|
||||||
}
|
|
||||||
}, [question, savedAnswer]);
|
|
||||||
|
|
||||||
const handleLeftClick = (index) => {
|
|
||||||
setSelectedLeft(index);
|
|
||||||
const status = pairs.findIndex(item => item.right === rightOptions[selectedRight]);
|
|
||||||
if (selectedRight !== null) {
|
|
||||||
makePair(index, selectedRight, status);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRightClick = (index) => {
|
|
||||||
setSelectedRight(index);
|
|
||||||
const status = pairs.findIndex(item => item.right === rightOptions[index]);
|
|
||||||
if (selectedLeft !== null) {
|
|
||||||
makePair(selectedLeft, index, status);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const makePair = (leftIndex, rightIndex, changePair) => {
|
|
||||||
const newPairs = [...pairs];
|
|
||||||
if (changePair > -1) {
|
|
||||||
setIsComplete(false);
|
|
||||||
pairs[changePair].right = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedRightValue = rightOptions[rightIndex];
|
|
||||||
newPairs[leftIndex].right = selectedRightValue;
|
|
||||||
setPairs(newPairs);
|
|
||||||
// console.log(newPairs);
|
|
||||||
|
|
||||||
setSelectedLeft(null);
|
|
||||||
setSelectedRight(null);
|
|
||||||
|
|
||||||
const allPairsMatched = newPairs.every((pair) => pair.right !== '');
|
|
||||||
if (allPairsMatched && !isComplete) {
|
|
||||||
setIsComplete(true);
|
|
||||||
|
|
||||||
const rightAnswers = newPairs.map((pair) => pair.right);
|
|
||||||
console.log(rightAnswers);
|
|
||||||
onAnswer(index, arrayToString(rightAnswers));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3>{question.TITLE}</h3>
|
|
||||||
<p>{question.QUESTION}</p>
|
|
||||||
{question.VIDEO && (
|
|
||||||
<video controls>
|
|
||||||
<source src={question.VIDEO} type="video/mp4" />
|
|
||||||
</video>
|
|
||||||
)}
|
|
||||||
{question.IMAGE && <img src={question.IMAGE} alt="Question" />}
|
|
||||||
{question.AUDIO && <audio controls src={question.AUDIO}></audio>}
|
|
||||||
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '20px' }}>
|
|
||||||
{/* Bagian kiri */}
|
|
||||||
<div>
|
|
||||||
{pairs.map((pair, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
onClick={() => handleLeftClick(index)}
|
|
||||||
style={{
|
|
||||||
padding: '10px',
|
|
||||||
margin: '10px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
backgroundColor: selectedLeft === index ? '#ccc' : pair.color,
|
|
||||||
color: 'white',
|
|
||||||
borderRadius: '5px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{pair.left}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Bagian kanan */}
|
|
||||||
<div>
|
|
||||||
{rightOptions.map((right, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
onClick={() => handleRightClick(index)}
|
|
||||||
style={{
|
|
||||||
padding: '10px',
|
|
||||||
margin: '10px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
backgroundColor:
|
|
||||||
pairs.find((pair) => pair.right === right)?.color ||
|
|
||||||
(selectedRight === index ? '#ccc' : '#f0f0f0'),
|
|
||||||
color: 'white',
|
|
||||||
borderRadius: '5px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{right}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MatchingPairsQuestion;
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const MultipleChoiceQuestion = ({ question, onAnswer, savedAnswer, index }) => {
|
|
||||||
const options = question.multipleChoices[0];
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
|
||||||
onAnswer(index, e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
function getOptionLetter(option) {
|
|
||||||
const match = option.match(/OPTION_(\w)/);
|
|
||||||
return match ? match[1] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3>{question.TITLE}</h3>
|
|
||||||
<p>{question.QUESTION}</p>
|
|
||||||
{question.VIDEO && (
|
|
||||||
<video controls>
|
|
||||||
<source src={question.VIDEO} type="video/mp4" />
|
|
||||||
</video>
|
|
||||||
)}
|
|
||||||
{question.IMAGE && <img src={question.IMAGE} alt="Question" />}
|
|
||||||
{question.AUDIO && <audio controls src={question.AUDIO}></audio>}
|
|
||||||
<div>
|
|
||||||
{['OPTION_A', 'OPTION_B', 'OPTION_C', 'OPTION_D', 'OPTION_E'].map(
|
|
||||||
(optionKey) => {
|
|
||||||
if (options[optionKey]) {
|
|
||||||
return (
|
|
||||||
<div key={optionKey}>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name={`question-${question.ID_ADMIN_EXERCISE}`}
|
|
||||||
value={getOptionLetter(optionKey)}
|
|
||||||
onChange={handleChange}
|
|
||||||
checked={savedAnswer === getOptionLetter(optionKey)}
|
|
||||||
/>
|
|
||||||
{options[optionKey]}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MultipleChoiceQuestion;
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const TrueFalseQuestion = ({ question, onAnswer, savedAnswer, index }) => {
|
|
||||||
const handleChange = (e) => {
|
|
||||||
onAnswer(index, e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h3>{question.TITLE}</h3>
|
|
||||||
<p>{question.QUESTION}</p>
|
|
||||||
{question.VIDEO && (
|
|
||||||
<video controls>
|
|
||||||
<source src={question.VIDEO} type="video/mp4" />
|
|
||||||
</video>
|
|
||||||
)}
|
|
||||||
{question.IMAGE && <img src={question.IMAGE} alt="Question" />}
|
|
||||||
{question.AUDIO && <audio controls src={question.AUDIO}></audio>}
|
|
||||||
<div>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name={`question-${question.ID_ADMIN_EXERCISE}`}
|
|
||||||
value="1"
|
|
||||||
onChange={handleChange}
|
|
||||||
checked={savedAnswer === '1'}
|
|
||||||
/>
|
|
||||||
True
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name={`question-${question.ID_ADMIN_EXERCISE}`}
|
|
||||||
value="0"
|
|
||||||
onChange={handleChange}
|
|
||||||
checked={savedAnswer === '0'}
|
|
||||||
/>
|
|
||||||
False
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TrueFalseQuestion;
|
|
||||||
|
|
@ -57,13 +57,12 @@ const useLevels = (section, topic) => {
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [section, topic]);
|
}, [section, topic]);
|
||||||
|
|
||||||
const isLevelUnlocked = (levelId) => {
|
const isLevelUnlocked = (levelIndex) => {
|
||||||
if (levelId !== 0) {
|
if (levelIndex !== 0) {
|
||||||
if (!lastCompletedLevel){
|
if (levels[levelIndex].CONTENT) {
|
||||||
return false;
|
return true;
|
||||||
}else{
|
}else{
|
||||||
const maxUnlock = extractLevelNumber(lastCompletedLevel.NAME_LEVEL);
|
return false;
|
||||||
return levelId <= maxUnlock;
|
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,6 @@ import Skeleton from 'react-loading-skeleton';
|
||||||
|
|
||||||
const Level = () => {
|
const Level = () => {
|
||||||
const { section, topic } = useParams();
|
const { section, topic } = useParams();
|
||||||
// const { isValidSection, isValidTopic } = useValidation();
|
|
||||||
|
|
||||||
// if (!isValidSection(section)) {
|
|
||||||
// return <Navigate to="/not-found" />;
|
|
||||||
// } else {
|
|
||||||
// if (!isValidTopic(topic)) {
|
|
||||||
// return <Navigate to="/not-found" />;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
const { levels, lastCompletedLevel, loading, error, isLevelUnlocked, levelDesc } = useLevels(section, topic);
|
const { levels, lastCompletedLevel, loading, error, isLevelUnlocked, levelDesc } = useLevels(section, topic);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user