story-generator/src/components/exercises/SentenceCompletion.tsx
Lucas Santana 2852b889b2
Some checks failed
Docker Build and Push / build (push) Has been cancelled
feat: Melhorando tracking
2025-01-12 14:15:07 -03:00

203 lines
6.2 KiB
TypeScript

import React, { useState, useRef } from 'react';
import { supabase } from '../../lib/supabase';
import { Loader2 } from 'lucide-react';
import { useStudentTracking } from '../../hooks/useStudentTracking';
interface SentenceCompletionProps {
story: {
id: string;
content: {
pages: Array<{
text: string;
}>;
};
};
studentId: string;
onComplete?: (score: number) => void;
}
export function SentenceCompletion({ story, studentId, onComplete }: SentenceCompletionProps) {
const [currentSentence, setCurrentSentence] = useState(0);
const [userAnswer, setUserAnswer] = useState('');
const [score, setScore] = useState(0);
const [isSubmitting, setIsSubmitting] = useState(false);
const [exerciseSentences, setExerciseSentences] = useState<Array<{
sentence: string;
answer: string;
}>>([]);
const [isLoading, setIsLoading] = useState(true);
const { trackExerciseCompleted } = useStudentTracking();
const startTime = useRef(Date.now());
const correctAnswers = useRef(0);
// Carregar palavras e preparar sentenças
React.useEffect(() => {
const loadExerciseWords = async () => {
try {
const { data: words, error } = await supabase
.from('story_exercise_words')
.select('*')
.eq('story_id', story.id)
.eq('exercise_type', 'completion');
if (error) throw error;
// Extrair todas as sentenças do texto
const allSentences = story.content.pages
.map(page => page.text.split(/[.!?]+/))
.flat()
.filter(Boolean)
.map(sentence => sentence.trim());
// Preparar exercícios com as palavras do banco
const exercises = allSentences
.filter(sentence =>
words?.some(word =>
sentence.toLowerCase().includes(word.word.toLowerCase())
)
)
.map(sentence => {
const word = words?.find(w =>
sentence.toLowerCase().includes(w.word.toLowerCase())
);
return {
sentence: sentence.replace(
new RegExp(word?.word || '', 'i'),
'_____'
),
answer: word?.word || ''
};
})
.filter(exercise => exercise.answer); // Remover exercícios sem resposta
setExerciseSentences(exercises);
setIsLoading(false);
} catch (error) {
console.error('Erro ao carregar palavras:', error);
setIsLoading(false);
}
};
loadExerciseWords();
}, [story.id, story.content.pages]);
if (isLoading) {
return (
<div className="max-w-2xl mx-auto p-6">
<div className="animate-pulse">
<div className="h-8 bg-gray-200 rounded w-1/3 mb-6" />
<div className="h-64 bg-gray-200 rounded" />
</div>
</div>
);
}
if (!exerciseSentences.length) {
return (
<div className="max-w-2xl mx-auto p-6">
<p className="text-gray-600">
Não foi possível gerar exercícios para este texto.
</p>
</div>
);
}
const currentExercise = exerciseSentences[currentSentence];
const handleSubmit = async () => {
setIsSubmitting(true);
try {
const isCorrect = userAnswer.toLowerCase() === currentExercise.answer.toLowerCase();
if (isCorrect) {
setScore(prev => prev + 10);
correctAnswers.current += 1;
}
// Avançar para próxima sentença ou finalizar
if (currentSentence < exerciseSentences.length - 1) {
setCurrentSentence(prev => prev + 1);
setUserAnswer('');
} else {
// Track exercise completion
const timeSpent = Date.now() - startTime.current;
trackExerciseCompleted({
exercise_id: `${story.id}_completion`,
story_id: story.id,
student_id: studentId,
exercise_type: 'completion',
score: Math.floor((correctAnswers.current / exerciseSentences.length) * 100),
time_spent: timeSpent,
answers_correct: correctAnswers.current,
answers_total: exerciseSentences.length,
difficulty_level: exerciseSentences.length <= 5 ? 'easy' : exerciseSentences.length <= 10 ? 'medium' : 'hard'
});
if (onComplete) {
onComplete(Math.floor((correctAnswers.current / exerciseSentences.length) * 100));
}
}
} catch (error) {
console.error('Erro ao verificar resposta:', error);
} finally {
setIsSubmitting(false);
}
};
return (
<div className="max-w-2xl mx-auto p-6">
<h2 className="text-2xl font-bold text-gray-900 mb-6">
Complete a Frase
</h2>
{/* Progresso */}
<div className="mb-6">
<div className="flex justify-between text-sm text-gray-500">
<span>Questão {currentSentence + 1} de {exerciseSentences.length}</span>
<span>{score} pontos</span>
</div>
<div className="mt-2 h-2 bg-gray-200 rounded-full overflow-hidden">
<div
className="h-full bg-purple-600 transition-all"
style={{ width: `${((currentSentence + 1) / exerciseSentences.length) * 100}%` }}
/>
</div>
</div>
{/* Sentença atual */}
<div className="mb-8">
<p className="text-xl leading-relaxed text-gray-700">
{currentExercise.sentence}
</p>
</div>
{/* Campo de resposta */}
<div className="mb-8">
<input
type="text"
value={userAnswer}
onChange={(e) => setUserAnswer(e.target.value)}
placeholder="Digite a palavra que completa a frase..."
className="w-full px-4 py-3 text-lg border border-gray-300 rounded-lg
focus:outline-none focus:ring-2 focus:ring-purple-500"
/>
</div>
{/* Botão de verificação */}
<button
onClick={handleSubmit}
disabled={!userAnswer || isSubmitting}
className="w-full py-3 bg-purple-600 text-white rounded-lg font-medium
hover:bg-purple-700 disabled:bg-gray-300 disabled:cursor-not-allowed
transition-colors"
>
{isSubmitting ? (
<Loader2 className="w-5 h-5 animate-spin mx-auto" />
) : (
'Verificar'
)}
</button>
</div>
);
}