mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-18 14:27:51 +00:00
203 lines
6.2 KiB
TypeScript
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>
|
|
);
|
|
}
|