youngtsai's picture
selectedAnswer
a697439
import React, { useState, useEffect } from 'react';
import {
Box,
Typography,
Radio,
RadioGroup,
FormControlLabel,
TextField,
Button,
Alert,
Collapse,
Stack,
Paper,
Divider
} from '@mui/material';
import LightbulbIcon from '@mui/icons-material/Lightbulb';
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
import axios from 'axios';
function Exercise({ exercise, onSubmit, onNextExercise, selectedAnswer: externalSelectedAnswer, onAnswerSelect, result: externalResult }) {
const [localSelectedAnswer, setLocalSelectedAnswer] = useState('');
const [showHints, setShowHints] = useState(false);
const [currentHintIndex, setCurrentHintIndex] = useState(0);
const [localResult, setLocalResult] = useState(null);
// 使用外部傳入的 selectedAnswer 和 result,如果有的話
const selectedAnswer = externalSelectedAnswer || localSelectedAnswer;
const result = externalResult || localResult;
// 當外部 selectedAnswer 變化時更新本地狀態
useEffect(() => {
if (externalSelectedAnswer) {
setLocalSelectedAnswer(externalSelectedAnswer);
}
}, [externalSelectedAnswer]);
// 當外部 result 變化時更新本地狀態
useEffect(() => {
if (externalResult) {
setLocalResult(externalResult);
}
}, [externalResult]);
const handleOptionClick = (value) => {
setLocalSelectedAnswer(value);
// 找到選中選項的文本
if (exercise.type === 'multiple_choice' && exercise.options) {
const optionIndex = parseInt(value, 10);
const optionText = exercise.options[optionIndex];
// 通知父組件選擇變化
if (onAnswerSelect) {
onAnswerSelect(value, optionText);
}
} else {
// 對於文本輸入,直接傳遞值
if (onAnswerSelect) {
onAnswerSelect(value, value);
}
}
};
const handleSubmit = async () => {
try {
if (!selectedAnswer.trim()) {
return;
}
// 找到選中選項的文本
let answerText = selectedAnswer;
if (exercise.type === 'multiple_choice' && exercise.options) {
const optionIndex = parseInt(selectedAnswer, 10);
answerText = exercise.options[optionIndex];
}
// 通知父組件
if (onSubmit) {
await onSubmit(selectedAnswer, answerText);
} else {
// 如果沒有提供 onSubmit,則使用本地邏輯
const response = await axios.post(`/api/exercises/check/${exercise.id}`, {
answer: selectedAnswer
});
setLocalResult(response.data);
}
} catch (error) {
console.error('Error submitting answer:', error);
setLocalResult({ correct: false, explanation: "檢查答案時發生錯誤" });
}
};
const handleNextExercise = () => {
setLocalSelectedAnswer('');
setShowHints(false);
setCurrentHintIndex(0);
setLocalResult(null);
if (onNextExercise) {
onNextExercise();
}
};
const showNextHint = () => {
if (currentHintIndex < exercise.hints.length - 1) {
setCurrentHintIndex(prev => prev + 1);
}
setShowHints(true);
};
return (
<Box sx={{
mb: 4,
display: 'flex',
gap: 3,
minHeight: 200
}}>
{/* 左側:題目和作答區域 */}
<Paper sx={{
flex: 2,
p: 2,
display: 'flex',
flexDirection: 'column',
backgroundColor: '#f8f9fa'
}}>
<Typography variant="h6" sx={{ mb: 2, color: 'text.primary' }}>
題目
</Typography>
<Typography variant="body1" sx={{
mb: 3,
fontWeight: 500,
whiteSpace: 'pre-wrap'
}}>
{exercise.question}
</Typography>
{/* 作答區域整合到題目下方 */}
<Box sx={{ mb: 2 }}>
{exercise.type === 'multiple_choice' ? (
<RadioGroup
value={selectedAnswer}
onChange={(e) => {
// 只有在還沒有結果或答錯的情況下才允許更改答案
if (!result?.correct) {
handleOptionClick(e.target.value);
}
}}
>
{exercise.options.map((option, index) => {
const isSelected = selectedAnswer === index.toString();
const isWrongAnswer = result && !result.correct && isSelected;
return (
<FormControlLabel
key={index}
value={index.toString()}
control={<Radio />}
label={`${String.fromCharCode(65 + index)}. ${option}`}
sx={{
mb: 1,
color: isWrongAnswer ? 'error.main' : 'text.primary',
'&.Mui-disabled': {
color: result?.correct ? 'success.main' : 'text.disabled'
}
}}
disabled={result?.correct}
/>
);
})}
</RadioGroup>
) : (
<TextField
fullWidth
value={selectedAnswer}
onChange={(e) => {
// 只有在還沒有結果或答錯的情況下才允許更改答案
if (!result?.correct) {
handleOptionClick(e.target.value);
}
}}
placeholder="請輸入答案"
variant="outlined"
size="small"
disabled={result?.correct}
error={result && !result.correct}
helperText={result && !result.correct ? "答案不正確" : ""}
/>
)}
</Box>
</Paper>
{/* 右側:操作和反饋區域 */}
<Paper sx={{
flex: 1,
p: 2,
display: 'flex',
flexDirection: 'column'
}}>
<Typography variant="h6" sx={{ mb: 2, color: 'text.primary' }}>
操作區
</Typography>
{/* 按鈕組 */}
<Stack spacing={2} sx={{ mb: 2 }}>
<Button
variant="contained"
onClick={handleSubmit}
disabled={!selectedAnswer.trim() || result?.correct}
fullWidth
>
提交答案
</Button>
<Button
variant="outlined"
startIcon={<LightbulbIcon />}
onClick={showNextHint}
disabled={showHints && currentHintIndex >= exercise.hints.length - 1}
fullWidth
>
{showHints ? '下一個提示' : '顯示提示'}
</Button>
{onNextExercise && (
<Button
variant="contained"
color="success"
endIcon={<NavigateNextIcon />}
onClick={handleNextExercise}
disabled={!result?.correct}
fullWidth
>
下一題
</Button>
)}
</Stack>
{/* 提示和結果區域 */}
<Box sx={{ mt: 'auto' }}>
<Collapse in={showHints}>
<Box sx={{ mb: 2 }}>
{exercise.hints.slice(0, currentHintIndex + 1).map((hint, index) => (
<Alert key={index} severity="info" sx={{ mb: 1 }}>
提示 {index + 1}: {hint}
</Alert>
))}
</Box>
</Collapse>
{result && (
<Alert
severity={result.correct ? "success" : "error"}
sx={{
'& .MuiAlert-message': { width: '100%' }
}}
>
<Box sx={{
display: 'flex',
flexDirection: 'column',
gap: 1
}}>
<Typography>
{result.correct ? '答對了!' : '再試一次!'}
</Typography>
{result.explanation && (
<Typography
sx={{
mt: 1,
p: 1,
backgroundColor: 'rgba(0, 0, 0, 0.04)',
borderRadius: 1
}}
>
解釋:{result.explanation}
</Typography>
)}
</Box>
</Alert>
)}
</Box>
</Paper>
</Box>
);
}
export default Exercise;