244 lines
7.6 KiB
JavaScript
244 lines
7.6 KiB
JavaScript
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(', ');
|
||
}
|
||
|
||
function extractAnswer(input) {
|
||
const lines = input.split('|');
|
||
|
||
const descriptions = lines.map(line => {
|
||
const parts = line.split('>');
|
||
return parts[1]?.trim();
|
||
});
|
||
|
||
return descriptions;
|
||
}
|
||
|
||
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) {
|
||
//if use "," as separator
|
||
// const arrSavedAnswer = stringToArray(savedAnswer);
|
||
|
||
//if use ">" as separator
|
||
const arrSavedAnswer = extractAnswer(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;
|