-u level & exercise hooks

This commit is contained in:
Dimas Atmodjo 2024-11-03 12:58:24 +07:00
parent 6a48573052
commit be9aba3355
26 changed files with 8 additions and 2519 deletions

View File

@ -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>

View File

@ -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) {

View File

@ -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;
} }
}; };

View File

@ -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'>

View File

@ -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
};
};

View File

@ -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
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,
};
};

View File

@ -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
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);