mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-18 14:27:51 +00:00
- Adiciona barra de progresso e feedback visual - Implementa lista de palavras encontradas - Melhora interatividade e estados visuais - Adiciona validação de palavras repetidas - Otimiza transições e animações - Mantém consistência com outros exercícios type: feat scope: exercises breaking: false
178 lines
5.1 KiB
TypeScript
178 lines
5.1 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { supabase } from '../../lib/supabase';
|
|
import { Loader2 } from 'lucide-react';
|
|
|
|
interface SentenceCompletionProps {
|
|
story: {
|
|
id: string;
|
|
content: {
|
|
pages: Array<{
|
|
text: string;
|
|
}>;
|
|
};
|
|
};
|
|
}
|
|
|
|
export function SentenceCompletion({ story }: 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);
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Avançar para próxima sentença
|
|
if (currentSentence < exerciseSentences.length - 1) {
|
|
setCurrentSentence(prev => prev + 1);
|
|
setUserAnswer('');
|
|
}
|
|
|
|
} 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>
|
|
);
|
|
}
|