feat: Melhorando tracking
Some checks failed
Docker Build and Push / build (push) Has been cancelled

This commit is contained in:
Lucas Santana 2025-01-12 14:15:07 -03:00
parent 3cdd136a4e
commit 2852b889b2
7 changed files with 251 additions and 17 deletions

View File

@ -1,12 +1,16 @@
import React, { useState, useRef } from 'react';
import { supabase } from '../../lib/supabase';
import { Mic, Square, Play, Loader2, ArrowRight } from 'lucide-react';
import { useStudentTracking } from '../../hooks/useStudentTracking';
interface PronunciationPracticeProps {
words: string[];
storyId: string;
studentId: string;
onComplete?: (score: number) => void;
}
export function PronunciationPractice({ words }: PronunciationPracticeProps) {
export function PronunciationPractice({ words, storyId, studentId, onComplete }: PronunciationPracticeProps) {
const [currentWordIndex, setCurrentWordIndex] = useState(0);
const [isRecording, setIsRecording] = useState(false);
const [audioBlob, setAudioBlob] = useState<Blob | null>(null);
@ -16,6 +20,10 @@ export function PronunciationPractice({ words }: PronunciationPracticeProps) {
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
const chunksRef = useRef<Blob[]>([]);
const { trackExerciseCompleted } = useStudentTracking();
const startTime = useRef(Date.now());
const wordsAttempted = useRef(0);
const wordsCorrect = useRef(0);
// Verificar se há palavras para praticar
if (!words.length) {
@ -69,15 +77,41 @@ export function PronunciationPractice({ words }: PronunciationPracticeProps) {
const handleNextWord = async () => {
setIsSubmitting(true);
try {
// Aqui você pode implementar a lógica de análise da pronúncia
// Por enquanto, vamos apenas avançar e dar pontos
setScore(prev => prev + 10);
// Simular análise de pronúncia (você pode substituir por uma análise real)
const wordScore = Math.floor(Math.random() * 30) + 70; // Score entre 70-100
const newScore = score + wordScore;
setScore(newScore);
wordsAttempted.current += 1;
if (wordScore >= 80) {
wordsCorrect.current += 1;
}
if (currentWordIndex < words.length - 1) {
setCurrentWordIndex(prev => prev + 1);
setAudioBlob(null);
} else {
setCompleted(true);
const timeSpent = Date.now() - startTime.current;
// Track exercise completion
trackExerciseCompleted({
exercise_id: `${storyId}_pronunciation`,
story_id: storyId,
student_id: studentId,
exercise_type: 'pronunciation',
score: Math.floor(newScore / words.length),
time_spent: timeSpent,
words_attempted: wordsAttempted.current,
words_correct: wordsCorrect.current,
pronunciation_score: Math.floor(newScore / words.length),
fluency_score: 85, // Este valor poderia vir de uma análise real de fluência
difficulty_level: words.length <= 5 ? 'easy' : words.length <= 10 ? 'medium' : 'hard'
});
if (onComplete) {
onComplete(Math.floor(newScore / words.length));
}
}
} catch (error) {
console.error('Erro ao processar áudio:', error);

View File

@ -1,6 +1,7 @@
import React, { useState } from 'react';
import React, { useState, useRef } from 'react';
import { supabase } from '../../lib/supabase';
import { Loader2 } from 'lucide-react';
import { useStudentTracking } from '../../hooks/useStudentTracking';
interface SentenceCompletionProps {
story: {
@ -11,9 +12,11 @@ interface SentenceCompletionProps {
}>;
};
};
studentId: string;
onComplete?: (score: number) => void;
}
export function SentenceCompletion({ story }: SentenceCompletionProps) {
export function SentenceCompletion({ story, studentId, onComplete }: SentenceCompletionProps) {
const [currentSentence, setCurrentSentence] = useState(0);
const [userAnswer, setUserAnswer] = useState('');
const [score, setScore] = useState(0);
@ -23,6 +26,9 @@ export function SentenceCompletion({ story }: SentenceCompletionProps) {
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(() => {
@ -105,12 +111,31 @@ export function SentenceCompletion({ story }: SentenceCompletionProps) {
if (isCorrect) {
setScore(prev => prev + 10);
correctAnswers.current += 1;
}
// Avançar para próxima sentença
// 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) {

View File

@ -1,9 +1,12 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { supabase } from '../../lib/supabase';
import { useStudentTracking } from '../../hooks/useStudentTracking';
interface WordFormationProps {
words: string[];
storyId: string;
studentId: string;
onComplete?: (score: number) => void;
}
interface SyllableWord {
@ -11,7 +14,7 @@ interface SyllableWord {
syllables: string[];
}
export function WordFormation({ words, storyId }: WordFormationProps) {
export function WordFormation({ words, storyId, studentId, onComplete }: WordFormationProps) {
const [availableSyllables, setAvailableSyllables] = useState<string[]>([]);
const [userWord, setUserWord] = useState<string>('');
const [score, setScore] = useState(0);
@ -22,6 +25,9 @@ export function WordFormation({ words, storyId }: WordFormationProps) {
type: 'success' | 'error';
message: string;
} | null>(null);
const { trackExerciseCompleted } = useStudentTracking();
const startTime = useRef(Date.now());
const correctAnswers = useRef(0);
useEffect(() => {
const loadWords = async () => {
@ -105,11 +111,32 @@ export function WordFormation({ words, storyId }: WordFormationProps) {
if (matchedWord && !completedWords.includes(matchedWord.word)) {
setScore(prev => prev + 10);
correctAnswers.current += 1;
setCompletedWords(prev => [...prev, matchedWord.word]);
setShowFeedback({
type: 'success',
message: 'Parabéns! Palavra correta!'
});
// Se completou todas as palavras
if (completedWords.length + 1 === targetWords.length) {
const timeSpent = Date.now() - startTime.current;
trackExerciseCompleted({
exercise_id: `${storyId}_word_formation`,
story_id: storyId,
student_id: studentId,
exercise_type: 'word_formation',
score: score + 10,
time_spent: timeSpent,
words_formed: correctAnswers.current,
words_correct: correctAnswers.current,
difficulty_level: targetWords.length <= 5 ? 'easy' : targetWords.length <= 10 ? 'medium' : 'hard'
});
if (onComplete) {
onComplete(score + 10);
}
}
} else if (completedWords.includes(matchedWord?.word || '')) {
setShowFeedback({
type: 'error',

View File

@ -2,6 +2,7 @@ import React, { useState, useRef } from 'react';
import { Mic, Square, Loader, Play, Upload } from 'lucide-react';
import { supabase } from '../../lib/supabase';
import { v4 as uuidv4 } from 'uuid';
import { useStudentTracking } from '../../hooks/useStudentTracking';
interface AudioRecorderProps {
storyId: string;
@ -17,6 +18,8 @@ export function AudioRecorder({ storyId, studentId, onAudioUploaded }: AudioReco
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
const chunksRef = useRef<Blob[]>([]);
const { trackAudioRecorded } = useStudentTracking();
const startTime = React.useRef<number | null>(null);
const startRecording = async () => {
try {
@ -34,6 +37,7 @@ export function AudioRecorder({ storyId, studentId, onAudioUploaded }: AudioReco
};
mediaRecorderRef.current.start();
startTime.current = Date.now();
setIsRecording(true);
setError(null);
} catch (err) {

View File

@ -4,6 +4,7 @@ import { supabase } from '../../lib/supabase';
import { useSession } from '../../hooks/useSession';
import { useStoryCategories } from '../../hooks/useStoryCategories';
import { Wand2, ArrowLeft, ArrowRight } from 'lucide-react';
import { useStudentTracking } from '../../hooks/useStudentTracking';
interface Category {
id: string;
@ -45,6 +46,8 @@ export function StoryGenerator() {
const [generationStatus, setGenerationStatus] = React.useState<
'idle' | 'creating' | 'generating-images' | 'saving'
>('idle');
const { trackStoryGenerated } = useStudentTracking();
const startTime = React.useRef(Date.now());
const steps: StoryStep[] = [
{
@ -150,6 +153,23 @@ export function StoryGenerator() {
if (updateError) throw updateError;
// Track story generation
const selectedTheme = themes?.find(t => t.id === choices.theme_id)?.title || '';
const generationTime = Date.now() - startTime.current;
const wordCount = updatedStory.content.pages.reduce((acc, page) =>
acc + page.text.split(/\s+/).length, 0);
trackStoryGenerated({
story_id: story.id,
theme: selectedTheme,
prompt: JSON.stringify(choices),
generation_time: generationTime,
word_count: wordCount,
student_id: session.user.id,
school_id: session.user.user_metadata?.school_id,
class_id: session.user.user_metadata?.class_id
});
navigate(`/aluno/historias/${story.id}`);
} catch (err) {
console.error('Erro ao gerar história:', err);

View File

@ -0,0 +1,94 @@
import { useRudderstack } from './useRudderstack';
interface StoryGeneratedProps {
story_id: string;
theme: string;
prompt: string;
generation_time: number;
word_count: number;
student_id: string;
class_id?: string;
school_id?: string;
}
interface AudioRecordedProps {
story_id: string;
duration: number;
file_size: number;
student_id: string;
page_number: number;
device_type: string;
recording_quality: string;
}
interface ExerciseCompletedProps {
exercise_id: string;
story_id: string;
student_id: string;
exercise_type: 'completion' | 'pronunciation' | 'word_formation';
score: number;
time_spent: number;
answers_correct?: number;
answers_total?: number;
words_attempted?: number;
words_correct?: number;
pronunciation_score?: number;
fluency_score?: number;
words_formed?: number;
difficulty_level: string;
}
interface InterestActionProps {
student_id: string;
category: string;
item: string;
total_interests: number;
interests_in_category: number;
}
export function useStudentTracking() {
const { track } = useRudderstack();
const trackStoryGenerated = (properties: StoryGeneratedProps) => {
track('story_generated', {
...properties,
timestamp: new Date().toISOString()
});
};
const trackAudioRecorded = (properties: AudioRecordedProps) => {
track('audio_recorded', {
...properties,
timestamp: new Date().toISOString()
});
};
const trackExerciseCompleted = (properties: ExerciseCompletedProps) => {
track('exercise_completed', {
...properties,
timestamp: new Date().toISOString()
});
};
const trackInterestAdded = (properties: InterestActionProps) => {
track('interest_added', {
...properties,
timestamp: new Date().toISOString()
});
};
const trackInterestRemoved = (properties: InterestActionProps) => {
track('interest_removed', {
...properties,
timestamp: new Date().toISOString()
});
};
return {
trackStoryGenerated,
trackAudioRecorded,
trackExerciseCompleted,
trackInterestAdded,
trackInterestRemoved
};
}

View File

@ -7,6 +7,7 @@ import React from 'react';
import { Heart, Gamepad2, Dog, Map, Pizza, School as SchoolIcon, Tv, Music, Palette, Trophy, Loader2 } from 'lucide-react';
import { supabase } from '../../lib/supabase';
import { useToast } from '../../hooks/useToast';
import { useStudentTracking } from '../../hooks/useStudentTracking';
interface InterestState {
category: string;
@ -15,6 +16,7 @@ interface InterestState {
export function StudentSettingsPage() {
const { toast } = useToast();
const { trackInterestAdded, trackInterestRemoved } = useStudentTracking();
const [interests, setInterests] = React.useState<InterestState[]>([
{ category: 'pets', items: [] },
{ category: 'entertainment', items: [] },
@ -105,13 +107,27 @@ export function StudentSettingsPage() {
if (error) throw error;
// Atualiza o estado local
setInterests(prev =>
prev.map(interest =>
setInterests(prev => {
const newInterests = prev.map(interest =>
interest.category === category
? { ...interest, items: [...interest.items, value] }
: interest
)
);
);
// Track interest added
const categoryInterests = newInterests.find(i => i.category === category);
if (categoryInterests) {
trackInterestAdded({
student_id: session.user.id,
category: category,
item: value,
total_interests: newInterests.reduce((acc, curr) => acc + curr.items.length, 0),
interests_in_category: categoryInterests.items.length
});
}
return newInterests;
});
toast({
title: 'Sucesso',
@ -152,13 +168,27 @@ export function StudentSettingsPage() {
if (error) throw error;
// Atualiza o estado local
setInterests(prev =>
prev.map(interest =>
setInterests(prev => {
const newInterests = prev.map(interest =>
interest.category === category
? { ...interest, items: interest.items.filter(i => i !== item) }
: interest
)
);
);
// Track interest removed
const categoryInterests = newInterests.find(i => i.category === category);
if (categoryInterests) {
trackInterestRemoved({
student_id: session.user.id,
category: category,
item: item,
total_interests: newInterests.reduce((acc, curr) => acc + curr.items.length, 0),
interests_in_category: categoryInterests.items.length
});
}
return newInterests;
});
toast({
title: 'Sucesso',