228 lines
7.3 KiB
React
228 lines
7.3 KiB
React
|
|
import React, { useState, useEffect } from 'react';
|
|||
|
|
|
|||
|
|
import MediaViewer from './MediaViewer';
|
|||
|
|
import { MEDIA_URL } from '../../../../../utils/Constant';
|
|||
|
|
|
|||
|
|
// 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, studentAnswer, index }) => {
|
|||
|
|
const savedAnswer = studentAnswer !== null ? studentAnswer : null;
|
|||
|
|
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 !== null) {
|
|||
|
|
const arrSavedAnswer = stringToArray(savedAnswer);
|
|||
|
|
const updatedPairs = initialPairs.map((pair, index) => ({
|
|||
|
|
...pair,
|
|||
|
|
right: arrSavedAnswer[index],
|
|||
|
|
}));
|
|||
|
|
setPairs(updatedPairs);
|
|||
|
|
} else {
|
|||
|
|
setPairs(initialPairs);
|
|||
|
|
}
|
|||
|
|
}, [question, savedAnswer]);
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
setIsShuffled(true);
|
|||
|
|
setRightOptions(shuffleArray(question.matchingPairs.map((pair) => pair.RIGHT_PAIR)));
|
|||
|
|
}, [question]);
|
|||
|
|
|
|||
|
|
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);
|
|||
|
|
onAnswer(index, arrayToString(rightAnswers), question.ID_ADMIN_EXERCISE);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const mediaUrls = [];
|
|||
|
|
const mediaPath = `${MEDIA_URL}/exercise`;
|
|||
|
|
|
|||
|
|
if (question.IMAGE) mediaUrls.push(`${mediaPath}/image/${question.IMAGE}`);
|
|||
|
|
if (question.AUDIO) mediaUrls.push(`${mediaPath}/audio/${question.AUDIO}`);
|
|||
|
|
if (question.VIDEO) mediaUrls.push(question.VIDEO);
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
|
|||
|
|
<div className={`correction-label ${question.IS_CORRECT === 1 ? `correct` : `incorrect` }`}>
|
|||
|
|
<i className={`me-2 bi ${question.IS_CORRECT === 1 ? `bi-check-circle-fill` : `bi-x-circle-fill` }`}></i>
|
|||
|
|
Your matching pair’s answers are {question.RESULT_SCORE_STUDENT}/{question.SCORE_WEIGHT} correct.
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{mediaUrls.length > 0 && <MediaViewer mediaUrls={mediaUrls} />}
|
|||
|
|
|
|||
|
|
<p>{question.QUESTION.split('\n').map((line, index) => (
|
|||
|
|
<React.Fragment key={index}>
|
|||
|
|
{line}
|
|||
|
|
<br />
|
|||
|
|
</React.Fragment>
|
|||
|
|
))}</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`}
|
|||
|
|
style={{
|
|||
|
|
display: 'flex', alignItems: 'center',
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<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`}
|
|||
|
|
style={{
|
|||
|
|
display: 'flex', alignItems: 'center',
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
<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;
|