story-generator/src/pages/student-dashboard/ExercisePage.tsx
Lucas Santana e4c225ebd7 feat: Adiciona toggle de texto maiúsculo para apoio à alfabetização
- 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
2025-01-23 15:29:08 -03:00

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>
);
}