mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-18 06:17:56 +00:00
- Implementa componente TextCaseToggle para alternância de caixa - Cria sistema de texto adaptativo com componentes AdaptiveText - Adiciona hook useUppercasePreference para gerenciar estado - Integra funcionalidade em todas as páginas principais - Persiste preferência do usuário no banco de dados
205 lines
5.9 KiB
TypeScript
205 lines
5.9 KiB
TypeScript
import React from 'react';
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
import { supabase } from '../../lib/supabase';
|
|
import { WordFormation } from '../../components/exercises/WordFormation';
|
|
import { SentenceCompletion } from '../../components/exercises/SentenceCompletion';
|
|
import { PronunciationPractice } from '../../components/exercises/PronunciationPractice';
|
|
import { ArrowLeft } from 'lucide-react';
|
|
import { TextCaseToggle } from '../../components/ui/text-case-toggle';
|
|
import { AdaptiveText, AdaptiveTitle, AdaptiveParagraph } from '../../components/ui/adaptive-text';
|
|
import { useUppercasePreference } from '../../hooks/useUppercasePreference';
|
|
import { useSession } from '../../hooks/useSession';
|
|
|
|
interface ExerciseWord {
|
|
word: string;
|
|
exercise_type: string;
|
|
phonemes: string[] | null;
|
|
syllable_pattern: string | null;
|
|
}
|
|
|
|
interface StoryRecording {
|
|
id: string;
|
|
created_at: string;
|
|
improvements: string[];
|
|
}
|
|
|
|
interface Story {
|
|
id: string;
|
|
story_recordings: StoryRecording[];
|
|
// ... outros campos necessários
|
|
}
|
|
|
|
export function ExercisePage() {
|
|
const { id: storyId, type } = useParams();
|
|
const navigate = useNavigate();
|
|
const [exerciseData, setExerciseData] = React.useState<any>(null);
|
|
const [exerciseWords, setExerciseWords] = React.useState<ExerciseWord[]>([]);
|
|
const [error, setError] = React.useState<string | null>(null);
|
|
const { session } = useSession();
|
|
const { isUpperCase, toggleUppercase, isLoading } = useUppercasePreference(session?.user?.id);
|
|
|
|
React.useEffect(() => {
|
|
const loadExerciseData = async () => {
|
|
try {
|
|
// Adicionar log para debug
|
|
console.log('Carregando exercício:', { storyId, type });
|
|
|
|
// Buscar história
|
|
const { data: story, error: storyError } = await supabase
|
|
.from('stories')
|
|
.select(`
|
|
*,
|
|
story_recordings (
|
|
id,
|
|
improvements,
|
|
created_at
|
|
)
|
|
`)
|
|
.eq('id', storyId)
|
|
.single();
|
|
|
|
if (storyError) throw storyError;
|
|
|
|
// Mapear tipo do exercício para o valor correto no banco
|
|
const exerciseType = type === 'pronunciation-practice'
|
|
? 'pronunciation'
|
|
: type === 'word-formation'
|
|
? 'formation'
|
|
: 'completion';
|
|
|
|
// Buscar palavras do exercício
|
|
const { data: words, error: wordsError } = await supabase
|
|
.from('story_exercise_words')
|
|
.select('*')
|
|
.eq('story_id', storyId)
|
|
.eq('exercise_type', exerciseType) // Usando o tipo mapeado
|
|
.order('created_at', { ascending: true });
|
|
|
|
// Adicionar log para debug
|
|
console.log('Palavras encontradas:', words);
|
|
|
|
if (wordsError) throw wordsError;
|
|
|
|
if (!story) {
|
|
throw new Error('História não encontrada');
|
|
}
|
|
|
|
// Ordenar gravações por data e pegar a mais recente
|
|
const latestRecording = (story as Story).story_recordings
|
|
.sort((a: StoryRecording, b: StoryRecording) =>
|
|
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
|
)[0];
|
|
|
|
setExerciseData({
|
|
story,
|
|
metrics: latestRecording,
|
|
});
|
|
|
|
setExerciseWords(words || []);
|
|
|
|
} catch (err) {
|
|
console.error('Erro ao carregar dados:', err);
|
|
setError('Não foi possível carregar os dados do exercício');
|
|
}
|
|
};
|
|
|
|
loadExerciseData();
|
|
}, [storyId, type]);
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="container mx-auto p-6">
|
|
<div className="text-center">
|
|
<p className="text-red-600 mb-4">{error}</p>
|
|
<button
|
|
onClick={() => navigate(-1)}
|
|
className="text-purple-600 hover:text-purple-700"
|
|
>
|
|
Voltar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!exerciseData) {
|
|
return (
|
|
<div className="container 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>
|
|
);
|
|
}
|
|
|
|
const renderExercise = () => {
|
|
if (!exerciseData?.story) {
|
|
return <div>Carregando...</div>;
|
|
}
|
|
|
|
switch (type) {
|
|
case 'word-formation':
|
|
return (
|
|
<WordFormation
|
|
words={exerciseWords.map(w =>
|
|
isUpperCase ? w.word.toUpperCase() : w.word
|
|
)}
|
|
storyId={storyId as string}
|
|
studentId={exerciseData.story.student_id}
|
|
/>
|
|
);
|
|
case 'sentence-completion':
|
|
return (
|
|
<SentenceCompletion
|
|
story={exerciseData.story}
|
|
studentId={exerciseData.story.student_id}
|
|
/>
|
|
);
|
|
case 'pronunciation-practice':
|
|
return (
|
|
<PronunciationPractice
|
|
words={exerciseWords.map(w => w.word)}
|
|
storyId={storyId as string}
|
|
studentId={exerciseData.story.student_id}
|
|
/>
|
|
);
|
|
default:
|
|
return <div>Exercício não encontrado</div>;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="container mx-auto p-6">
|
|
{/* Cabeçalho */}
|
|
<div className="mb-8">
|
|
<button
|
|
onClick={() => navigate(-1)}
|
|
className="flex items-center gap-2 text-gray-600 hover:text-gray-900 mb-4"
|
|
>
|
|
<ArrowLeft className="w-5 h-5" />
|
|
<AdaptiveText
|
|
text="Voltar para história"
|
|
isUpperCase={isUpperCase}
|
|
/>
|
|
</button>
|
|
|
|
<div className="flex justify-between items-center">
|
|
<AdaptiveTitle
|
|
text={`Exercício: ${type?.replace('-', ' ')}`}
|
|
isUpperCase={isUpperCase}
|
|
className="text-xl font-bold"
|
|
/>
|
|
<TextCaseToggle
|
|
isUpperCase={isUpperCase}
|
|
onToggle={toggleUppercase}
|
|
isLoading={isLoading}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Exercício */}
|
|
{renderExercise()}
|
|
</div>
|
|
);
|
|
}
|