Skip to content

Commit fbb25d8

Browse files
committed
refactor: 채점 오류 개선
1 parent a160d88 commit fbb25d8

File tree

1 file changed

+133
-56
lines changed

1 file changed

+133
-56
lines changed

src/components/sections/TodayQuizSection.tsx

Lines changed: 133 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ const TodayQuizSection: React.FC = () => {
7575
const [isCorrectFromAI, setIsCorrectFromAI] = useState<boolean | null>(null);
7676
const [resultChars, setResultChars] = useState<string[]>([]);
7777
const [feedbackChars, setFeedbackChars] = useState<string[]>([]);
78+
const [isDuplicateAnswer, setIsDuplicateAnswer] = useState<boolean>(false);
7879
const { openModal } = useModal();
7980
const sseConnectionRef = useRef<{ close: () => void } | null>(null);
8081

@@ -137,11 +138,18 @@ const TodayQuizSection: React.FC = () => {
137138
const timer = setTimeout(() => {
138139
const newPercentages: {[key: number]: number} = {};
139140
[1, 2, 3, 4].forEach(choice => {
140-
// 사용자의 선택을 포함한 새로운 비율 계산
141141
const originalRate = selectionRates.selectionRates[choice.toString()] || 0;
142142
const originalCount = Math.round(originalRate * selectionRates.totalCount);
143-
const newCount = selectedAnswer === choice ? originalCount + 1 : originalCount;
144-
const newTotalCount = selectionRates.totalCount + 1;
143+
144+
// 중복 답변이 아닌 경우에만 새로운 선택을 카운트에 추가
145+
let newCount = originalCount;
146+
let newTotalCount = selectionRates.totalCount;
147+
148+
if (!isDuplicateAnswer) {
149+
newCount = selectedAnswer === choice ? originalCount + 1 : originalCount;
150+
newTotalCount = selectionRates.totalCount + 1;
151+
}
152+
145153
const newRate = newCount / newTotalCount;
146154
newPercentages[choice] = Math.round(newRate * 100);
147155
});
@@ -150,7 +158,7 @@ const TodayQuizSection: React.FC = () => {
150158

151159
return () => clearTimeout(timer);
152160
}
153-
}, [isSubmitted, selectionRates, selectedAnswer]);
161+
}, [isSubmitted, selectionRates, selectedAnswer, isDuplicateAnswer]);
154162

155163
const { data: question, isLoading } = useQuery({
156164
queryKey: ['todayQuiz', subscriptionId, quizId],
@@ -252,51 +260,114 @@ const TodayQuizSection: React.FC = () => {
252260
}
253261
const submitResponse = await quizAPI.submitTodayQuizAnswer(quizId, submitAnswer, subscriptionId);
254262

255-
// submitResponse에서 userQuizAnswerId 추출 (모든 타입에서 공통)
256-
let userQuizAnswerId: string;
257-
263+
// 응답에서 UserQuizAnswerResponseDto 추출
264+
let responseData;
258265
if (submitResponse && typeof submitResponse === 'object') {
259-
if ('data' in submitResponse && submitResponse.data) {
260-
userQuizAnswerId = (submitResponse.data as any).toString();
261-
} else if ('userQuizAnswerId' in submitResponse) {
262-
userQuizAnswerId = (submitResponse as any).userQuizAnswerId.toString();
263-
} else {
264-
userQuizAnswerId = (submitResponse as any).toString();
265-
}
266+
responseData = ('data' in submitResponse) ? submitResponse.data : submitResponse;
266267
} else {
267-
userQuizAnswerId = (submitResponse as any).toString();
268+
responseData = submitResponse;
268269
}
269270

270-
// 모든 타입에서 평가 API 호출
271-
try {
272-
await quizAPI.evaluateQuizAnswer(userQuizAnswerId);
273-
} catch (evaluateError) {
274-
console.error('답안 평가 요청 실패:', evaluateError);
275-
// 평가 API 실패해도 계속 진행
276-
}
277-
278-
if (displayQuiz.quizType === 'MULTIPLE_CHOICE') {
279-
// 객관식: 클라이언트에서 정답 검증
280-
const correctAnswerNumber = parseInt(displayQuiz.answerNumber || '1');
281-
const isCorrect = selectedAnswer === correctAnswerNumber;
282-
const choiceText = displayQuiz[`choice${correctAnswerNumber}` as keyof QuizData] as string;
283-
const cleanAnswerText = choiceText ? choiceText.replace(/^\d+\.\s*/, '') : '';
284-
const answerText = `${correctAnswerNumber}번. ${cleanAnswerText}`;
271+
// 중복 답변 체크
272+
if ((responseData as any)?.duplicated) {
273+
// 중복 답변 상태 설정
274+
setIsDuplicateAnswer(true);
285275

286-
const result: AnswerResult = {
287-
isCorrect,
288-
answer: answerText,
289-
commentary: displayQuiz.commentary
276+
// 중복 답변인 경우 모달 표시하고 기존 답안 결과 보여주기
277+
openModal({
278+
title: '이미 답변한 문제입니다',
279+
content: (
280+
<div className="text-center py-4">
281+
<div className="w-16 h-16 mx-auto mb-4 bg-blue-100 rounded-full flex items-center justify-center">
282+
<svg className="w-8 h-8 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
283+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
284+
</svg>
285+
</div>
286+
<p className="text-gray-700 text-lg mb-4">이전에 답변한 내용을 확인해보세요!</p>
287+
</div>
288+
),
289+
size: 'sm'
290+
});
291+
292+
// 중복 답변인 경우 기존 답안 결과 표시
293+
const duplicateResult: AnswerResult = {
294+
isCorrect: (responseData as any)?.correct || false,
295+
answer: (responseData as any)?.answer || displayQuiz.answer || '',
296+
commentary: (responseData as any)?.commentary || displayQuiz.commentary
290297
};
291298

292-
setAnswerResult(result);
293-
setIsSubmitted(true);
294-
} else if (displayQuiz.quizType === 'SHORT_ANSWER') {
295-
// 주관식: 평가 API 호출하여 정답/오답 확인
299+
setAnswerResult(duplicateResult);
296300
setIsSubmitted(true);
297301

302+
// 중복 답변인 경우 이전 선택한 답안 복원 (객관식만)
303+
if (displayQuiz.quizType === 'MULTIPLE_CHOICE') {
304+
const userAnswer = (responseData as any)?.userAnswer;
305+
if (userAnswer) {
306+
// userAnswer에서 선택 번호 추출 (예: "1. text" -> 1)
307+
const answerMatch = userAnswer.match(/^(\d+)/);
308+
if (answerMatch) {
309+
setSelectedAnswer(parseInt(answerMatch[1]));
310+
}
311+
}
312+
313+
// 선택 비율 가져오기
314+
if (quizId) {
315+
try {
316+
const ratesResponse = await quizAPI.getQuizSelectionRates(quizId);
317+
if (ratesResponse && typeof ratesResponse === 'object') {
318+
const ratesData = ('data' in ratesResponse) ? ratesResponse.data : ratesResponse;
319+
setSelectionRates(ratesData as SelectionRatesData);
320+
}
321+
} catch (error) {
322+
console.error('중복 답변 - 선택 비율 데이터 가져오기 실패:', error);
323+
}
324+
}
325+
}
326+
327+
return; // 여기서 중단
328+
}
329+
330+
// responseData에서 userQuizAnswerId 추출 (모든 타입에서 공통)
331+
const userQuizAnswerId = (responseData as any)?.userQuizAnswerId?.toString() || '';
332+
333+
334+
if (displayQuiz.quizType === 'MULTIPLE_CHOICE') {
335+
// 객관식: evaluate API로 정답/오답 확인
336+
try {
337+
const evaluateResponse = await quizAPI.evaluateQuizAnswer(userQuizAnswerId);
338+
339+
// 평가 응답에서 결과 추출
340+
let evaluateData;
341+
if (evaluateResponse && typeof evaluateResponse === 'object') {
342+
evaluateData = ('data' in evaluateResponse) ? evaluateResponse.data : evaluateResponse;
343+
} else {
344+
evaluateData = evaluateResponse;
345+
}
346+
347+
const result: AnswerResult = {
348+
isCorrect: (evaluateData as any)?.correct || false,
349+
answer: (evaluateData as any)?.answer || displayQuiz.answer || '',
350+
commentary: (evaluateData as any)?.commentary || displayQuiz.commentary
351+
};
352+
353+
setAnswerResult(result);
354+
setIsSubmitted(true);
355+
} catch (evaluateError) {
356+
console.error('객관식 답안 평가 실패:', evaluateError);
357+
358+
// 평가 API 실패 시 응답 데이터 사용
359+
const fallbackResult: AnswerResult = {
360+
isCorrect: (responseData as any)?.correct || false,
361+
answer: (responseData as any)?.answer || displayQuiz.answer || '',
362+
commentary: (responseData as any)?.commentary || displayQuiz.commentary
363+
};
364+
365+
setAnswerResult(fallbackResult);
366+
setIsSubmitted(true);
367+
}
368+
} else if (displayQuiz.quizType === 'SHORT_ANSWER') {
369+
// 주관식: evaluate API로 정답/오답 확인
298370
try {
299-
// 평가 API 호출하여 정답/오답 결과 받기
300371
const evaluateResponse = await quizAPI.evaluateQuizAnswer(userQuizAnswerId);
301372

302373
// 평가 응답에서 결과 추출
@@ -307,33 +378,33 @@ const TodayQuizSection: React.FC = () => {
307378
evaluateData = evaluateResponse;
308379
}
309380

310-
// 평가 결과로 결과 설정
311381
const result: AnswerResult = {
312-
isCorrect: (evaluateData as any)?.isCorrect || false,
382+
isCorrect: (evaluateData as any)?.correct || false,
313383
answer: (evaluateData as any)?.answer || displayQuiz.answer || '',
314384
commentary: (evaluateData as any)?.commentary || displayQuiz.commentary
315385
};
316386

317387
setAnswerResult(result);
388+
setIsSubmitted(true);
318389
} catch (evaluateError) {
319390
console.error('주관식 답안 평가 실패:', evaluateError);
320391

321-
// 평가 API 실패 시 기본값으로 처리
392+
// 평가 API 실패 시 응답 데이터 사용
322393
const fallbackResult: AnswerResult = {
323-
isCorrect: false,
324-
answer: displayQuiz.answer || '',
325-
commentary: displayQuiz.commentary
394+
isCorrect: (responseData as any)?.correct || false,
395+
answer: (responseData as any)?.answer || displayQuiz.answer || '',
396+
commentary: (responseData as any)?.commentary || displayQuiz.commentary
326397
};
327398

328399
setAnswerResult(fallbackResult);
400+
setIsSubmitted(true);
329401
}
330402
} else if (displayQuiz.quizType === 'SUBJECTIVE') {
331-
// 서술형: AI 피드백 있음
332-
// 먼저 기본 결과 표시 (AI 피드백 없이)
403+
// 서술형: 응답 데이터로 초기 결과 설정 후 AI 피드백
333404
const initialResult: AnswerResult = {
334-
isCorrect: false, // 일단 false로 설정, AI 피드백에서 업데이트
335-
answer: displayQuiz.answer || '',
336-
commentary: displayQuiz.commentary
405+
isCorrect: (responseData as any)?.correct || false,
406+
answer: (responseData as any)?.answer || displayQuiz.answer || '',
407+
commentary: (responseData as any)?.commentary || displayQuiz.commentary
337408
};
338409

339410
setAnswerResult(initialResult);
@@ -572,7 +643,7 @@ const TodayQuizSection: React.FC = () => {
572643
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
573644
</svg>
574645
<span className="text-sm font-medium text-blue-700">
575-
{isSubmitted ? selectionRates.totalCount + 1 : selectionRates.totalCount}명이 이 문제를 풀었습니다
646+
{isSubmitted && !isDuplicateAnswer ? selectionRates.totalCount + 1 : selectionRates.totalCount}명이 이 문제를 풀었습니다
576647
</span>
577648
</div>
578649
)}
@@ -725,8 +796,8 @@ const TodayQuizSection: React.FC = () => {
725796
{/* Result Section - 제출 후에만 표시 */}
726797
{isSubmitted && answerResult && (
727798
<div className="max-w-4xl mx-auto mb-8">
728-
{/* 정답/오답 메시지 - 객관식, 주관식에 표시 FIXME: 주관식은 일단 제외)*/}
729-
{(displayQuiz?.quizType === 'MULTIPLE_CHOICE') && (
799+
{/* 정답/오답 메시지 - 모든 타입에 표시 */}
800+
{(displayQuiz?.quizType === 'MULTIPLE_CHOICE' || displayQuiz?.quizType === 'SHORT_ANSWER' || displayQuiz?.quizType === 'SUBJECTIVE') && (
730801
<div className={`inline-flex items-center rounded-full px-6 py-3 mb-6 ${
731802
answerResult.isCorrect ? 'bg-green-100' : 'bg-red-100'
732803
}`}>
@@ -885,9 +956,15 @@ const TodayQuizSection: React.FC = () => {
885956
const originalRate = selectionRates.selectionRates[choice.toString()] || 0;
886957
const originalCount = Math.round(originalRate * selectionRates.totalCount);
887958

888-
// 사용자의 선택을 포함해서 새로운 비율 계산
889-
const newCount = selectedAnswer === choice ? originalCount + 1 : originalCount;
890-
const newTotalCount = selectionRates.totalCount + 1;
959+
// 중복 답변이 아닌 경우에만 사용자의 선택을 포함해서 새로운 비율 계산
960+
let newCount = originalCount;
961+
let newTotalCount = selectionRates.totalCount;
962+
963+
if (!isDuplicateAnswer) {
964+
newCount = selectedAnswer === choice ? originalCount + 1 : originalCount;
965+
newTotalCount = selectionRates.totalCount + 1;
966+
}
967+
891968
const newRate = newCount / newTotalCount;
892969
const actualPercentage = Math.round(newRate * 100);
893970

0 commit comments

Comments
 (0)