Skip to content

Commit d0ac643

Browse files
authored
fix: Resolve assessment timer loop and course editor persistence issues (#180)
1 parent a641649 commit d0ac643

4 files changed

Lines changed: 51 additions & 36 deletions

File tree

src/components/professor/courseEditor/GeneralQuestionsManager.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export default function GeneralQuestionsManager({
4747

4848
return (
4949
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4">
50-
<Card className="w-full max-w-4xl max-h-[90vh] flex flex-col bg-white shadow-2xl">
50+
<Card className="w-full max-w-4xl max-h-[90vh] flex flex-col bg-white shadow-2xl overflow-hidden">
5151
<div className="flex items-center justify-between p-6 border-b">
5252
<div>
5353
<h2 className="text-2xl font-bold flex items-center gap-2">
@@ -194,4 +194,4 @@ export default function GeneralQuestionsManager({
194194
</Card>
195195
</div>
196196
);
197-
}
197+
}

src/hooks/useQuickSave.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
1-
import { useMutation, useQueryClient } from '@tanstack/react-query';
1+
import { useMutation } from '@tanstack/react-query';
22
import { courseService } from '../api/services/course.service';
33
import type { QuickSaveRequest } from '../types/entities';
44
import type { AxiosError } from 'axios';
55

6-
// Hook para guardado rápido sin validación completa
76
export const useQuickSave = () => {
8-
const queryClient = useQueryClient();
9-
107
return useMutation<
118
void,
129
AxiosError,
1310
{ courseId: string; data: QuickSaveRequest }
1411
>({
1512
mutationFn: ({ courseId, data }) => courseService.quickSave(courseId, data),
1613
onSuccess: () => {
17-
// Invalidar cache para reflejar cambios
18-
queryClient.invalidateQueries({ queryKey: ['professorCourses'] });
1914
},
2015
});
21-
};
16+
};

src/pages/Course/TakeAssessmentPage.tsx

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useParams, useNavigate } from 'react-router-dom';
2-
import { useState, useEffect, useCallback } from 'react';
2+
import { useState, useEffect, useCallback, useRef } from 'react';
33
import { ArrowLeft, Clock, AlertCircle, CheckCircle, Send } from 'lucide-react';
44
import Button from '../../components/ui/Button/Button';
55
import {
@@ -35,15 +35,17 @@ export default function TakeAssessmentPage() {
3535
QuestionForStudent[]
3636
>([]);
3737

38+
const hasAutoSubmittedRef = useRef(false);
39+
3840
const { data: assessment, isLoading } = useAssessment(assessmentId);
3941
const startAttemptMutation = useStartAttempt();
4042
const saveAnswersMutation = useSaveAnswers();
4143
const submitAttemptMutation = useSubmitAttempt();
4244

43-
const handleSubmit = useCallback(async () => {
45+
const handleSubmit = useCallback(async (forceSubmit = false) => {
4446
if (!attemptId) return;
4547

46-
if (Object.keys(answers).length === 0) {
48+
if (!forceSubmit && Object.keys(answers).length === 0) {
4749
alert('Debes responder al menos una pregunta para enviar la evaluación.');
4850
return;
4951
}
@@ -54,19 +56,29 @@ export default function TakeAssessmentPage() {
5456
answer,
5557
})
5658
);
57-
59+
5860
try {
59-
await submitAttemptMutation.mutateAsync({
60-
attemptId,
61-
submitData: { attemptId, answers: answersArray },
62-
});
61+
if (answersArray.length > 0) {
62+
await submitAttemptMutation.mutateAsync({
63+
attemptId,
64+
submitData: { attemptId, answers: answersArray },
65+
});
66+
67+
navigate(
68+
`/courses/${courseId}/assessments/${assessmentId}/results/${attemptId}`
69+
);
70+
} else if (forceSubmit) {
71+
toast.error("El tiempo se agotó y no hubo respuestas registradas.");
72+
navigate(`/courses/${courseId}/assessments`);
73+
}
6374

64-
navigate(
65-
`/courses/${courseId}/assessments/${assessmentId}/results/${attemptId}`
66-
);
6775
} catch (error) {
6876
console.error('Error submitting attempt:', error);
69-
alert('Hubo un error al enviar la evaluación.');
77+
if (forceSubmit) {
78+
navigate(`/courses/${courseId}/assessments`);
79+
} else {
80+
toast.error('Hubo un error al enviar la evaluación.');
81+
}
7082
}
7183
}, [
7284
attemptId,
@@ -91,13 +103,12 @@ export default function TakeAssessmentPage() {
91103
});
92104
setAttemptId(attempt.id);
93105
setHasStarted(true);
106+
hasAutoSubmittedRef.current = false;
94107

95-
// Guardamos las preguntas del intento (SIN respuestas correctas)
96108
if (attempt.assessment?.questions) {
97109
setAttemptQuestions(attempt.assessment.questions);
98110
}
99111

100-
// Si hay respuestas previas guardadas (auto-save), las cargamos
101112
if (attempt.answers && attempt.answers.length > 0) {
102113
const savedAnswers: Record<string, number | string> = {};
103114
attempt.answers.forEach((answer) => {
@@ -152,18 +163,27 @@ export default function TakeAssessmentPage() {
152163
}, [hasStarted, assessment, timeLeft]);
153164

154165
useEffect(() => {
166+
if (hasAutoSubmittedRef.current) return;
167+
155168
if (timeLeft !== null && timeLeft > 0) {
156169
const timer = setInterval(() => {
157-
setTimeLeft((prev) => (prev !== null ? prev - 1 : null));
170+
setTimeLeft((prev) => {
171+
if (prev !== null && prev <= 1) {
172+
clearInterval(timer);
173+
return 0;
174+
}
175+
return prev !== null ? prev - 1 : null;
176+
});
158177
}, 1000);
159178
return () => clearInterval(timer);
160179
} else if (timeLeft === 0) {
161-
handleSubmit();
180+
hasAutoSubmittedRef.current = true;
181+
handleSubmit(true);
162182
}
163183
}, [timeLeft, handleSubmit]);
164184

165185
useEffect(() => {
166-
if (hasStarted && attemptId && Object.keys(answers).length > 0) {
186+
if (hasStarted && attemptId && Object.keys(answers).length > 0 && !hasAutoSubmittedRef.current) {
167187
const autoSave = setInterval(() => {
168188
const answersArray = Object.entries(answers).map(
169189
([questionId, answer]) => ({
@@ -334,8 +354,8 @@ export default function TakeAssessmentPage() {
334354
</div>
335355
)}
336356
<Button
337-
onClick={handleSubmit}
338-
disabled={submitAttemptMutation.isPending}
357+
onClick={() => handleSubmit(false)} // Envío manual (no forzado)
358+
disabled={submitAttemptMutation.isPending || hasAutoSubmittedRef.current}
339359
size="sm"
340360
className="bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800 flex-1 sm:flex-initial"
341361
>
@@ -389,6 +409,7 @@ export default function TakeAssessmentPage() {
389409
parseInt(e.target.value, 10)
390410
)
391411
}
412+
disabled={hasAutoSubmittedRef.current}
392413
className="w-5 h-5 text-purple-600 focus:ring-purple-500"
393414
/>
394415
<span className="text-slate-700">{option.text}</span>
@@ -401,6 +422,7 @@ export default function TakeAssessmentPage() {
401422
onChange={(e) =>
402423
handleAnswerChange(question.id!, e.target.value)
403424
}
425+
disabled={hasAutoSubmittedRef.current}
404426
placeholder="Escribe tu respuesta aquí..."
405427
className="w-full min-h-[150px] p-4 border-2 border-slate-200 rounded-lg focus:border-purple-400 focus:ring-2 focus:ring-purple-200 resize-none"
406428
/>
@@ -412,8 +434,8 @@ export default function TakeAssessmentPage() {
412434
</div>
413435
<div className="mt-8 flex justify-center">
414436
<Button
415-
onClick={handleSubmit}
416-
disabled={submitAttemptMutation.isPending}
437+
onClick={() => handleSubmit(false)}
438+
disabled={submitAttemptMutation.isPending || hasAutoSubmittedRef.current}
417439
size="lg"
418440
className="bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800 px-8"
419441
>
@@ -426,4 +448,4 @@ export default function TakeAssessmentPage() {
426448
</div>
427449
</div>
428450
);
429-
}
451+
}

src/pages/Professor/ProfessorCourseEdition.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@ export default function ProfessorCourseEdition() {
171171
description: currentCourse.description,
172172
status: currentCourse.status || 'en-desarrollo',
173173
isFree: currentCourse.isFree,
174-
// Convertir centavos a pesos para mostrar en el formulario
175174
price: currentCourse.priceInCents
176175
? toAmount(currentCourse.priceInCents)
177176
: 0,
@@ -190,12 +189,12 @@ export default function ProfessorCourseEdition() {
190189

191190
setUnits(unitsFromBackend);
192191

193-
if (unitsFromBackend.length > 0 && selectedUnitId === null) {
194-
setSelectedUnitId(unitsFromBackend[0].unitNumber);
192+
if (unitsFromBackend.length > 0) {
193+
setSelectedUnitId((prev) => (prev === null ? unitsFromBackend[0].unitNumber : prev));
195194
}
196195
}
197196
}
198-
}, [courses, courseId, selectedUnitId]); // selectedUnitId agregado a dependencias
197+
}, [courses, courseId]);
199198

200199
// Efecto para auto-guardar cuando cambias de unidad
201200
useEffect(() => {
@@ -231,7 +230,6 @@ export default function ProfessorCourseEdition() {
231230
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
232231
}, [units, courseId, performSave]);
233232

234-
// ... (Handlers para unidades, preguntas, materiales igual que antes) ...
235233
const handleCreateUnit = () => {
236234
if (!courseId || !courses) return;
237235

0 commit comments

Comments
 (0)