refactor: atualiza interface de capa das histórias

- Adiciona tipagem para cover na interface Story
- Atualiza queries para usar story_pages como capa
- Usa página 1 como capa padrão das histórias
- Otimiza carregamento de imagens com parâmetros
This commit is contained in:
Lucas Santana 2024-12-23 18:21:32 -03:00
parent fbeeace8bb
commit 7087a87ece
6 changed files with 42 additions and 180 deletions

View File

@ -43,6 +43,11 @@ e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
- Uso da primeira página como capa - Uso da primeira página como capa
- Tamanho reduzido para thumbnails - Tamanho reduzido para thumbnails
- Carregamento lazy para melhor performance - Carregamento lazy para melhor performance
- Refatoração da interface de capa
- Tipagem específica para cover na interface Story
- Padronização do uso da primeira página como capa
- Otimização de queries para busca de capas
- Parâmetros de transformação para thumbnails
- Padronização da interface de histórias - Padronização da interface de histórias
- Consistência visual entre dashboard e lista - Consistência visual entre dashboard e lista

View File

@ -194,11 +194,11 @@ export function StoryPage() {
if (storyError) throw storyError; if (storyError) throw storyError;
// Ordenar páginas por número // Ordenar páginas por número
const orderedPages = storyData.story_pages.sort((a, b) => a.page_number - b.page_number); const orderedPages = storyData.story_pages.sort((a: { page_number: number }, b: { page_number: number }) => a.page_number - b.page_number);
setStory({ setStory({
...storyData, ...storyData,
content: { content: {
pages: orderedPages.map(page => ({ pages: orderedPages.map((page: { text: string; image_url: string }) => ({
text: page.text, text: page.text,
image: page.image_url image: page.image_url
})) }))
@ -301,9 +301,10 @@ export function StoryPage() {
// Pré-carregar próxima imagem // Pré-carregar próxima imagem
useEffect(() => { useEffect(() => {
if (story?.content?.pages?.[currentPage + 1]?.image) { const nextImageUrl = story?.content?.pages?.[currentPage + 1]?.image;
if (nextImageUrl) {
const nextImage = new Image(); const nextImage = new Image();
nextImage.src = story.content.pages[currentPage + 1].image; nextImage.src = nextImageUrl;
} }
}, [currentPage, story]); }, [currentPage, story]);

View File

@ -65,7 +65,7 @@ export function StudentDashboardPage() {
currentLevel: 3 // Exemplo currentLevel: 3 // Exemplo
}); });
// Buscar histórias recentes // Buscar histórias recentes com a primeira página como capa
const { data, error } = await supabase const { data, error } = await supabase
.from('stories') .from('stories')
.select(` .select(`
@ -75,7 +75,7 @@ export function StudentDashboardPage() {
) )
`) `)
.eq('student_id', session.user.id) .eq('student_id', session.user.id)
.eq('story_pages.page_number', 1) .eq('story_pages.page_number', 1) // Garante que pegamos a primeira página
.order('created_at', { ascending: false }) .order('created_at', { ascending: false })
.limit(3); .limit(3);

View File

@ -23,22 +23,35 @@ export function StudentStoriesPage() {
const { data: { session } } = await supabase.auth.getSession(); const { data: { session } } = await supabase.auth.getSession();
if (!session?.user?.id) return; if (!session?.user?.id) return;
const { data, error } = await supabase const query = supabase
.from('stories') .from('stories')
.select(` .select('*')
*, .eq('student_id', session.user.id);
cover:story_pages!inner(
image_url
)
`)
.eq('student_id', session.user.id)
.eq('story_pages.page_number', 1)
.order('created_at', { ascending: false });
if (statusFilter !== 'all') {
query.eq('status', statusFilter);
}
let { data, error } = await query;
if (error) throw error; if (error) throw error;
setStories(data || []);
// Aplicar ordenação
const sortedData = (data || []).sort((a, b) => {
switch (sortBy) {
case 'oldest':
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
case 'title':
return a.title.localeCompare(b.title);
case 'performance':
return (b.performance_score || 0) - (a.performance_score || 0);
default: // recent
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
}
});
setStories(sortedData);
} catch (err) { } catch (err) {
console.error('Erro ao carregar histórias:', err); console.error('Erro ao buscar histórias:', err);
setError('Não foi possível carregar suas histórias'); setError('Não foi possível carregar suas histórias');
} finally { } finally {
setLoading(false); setLoading(false);
@ -46,7 +59,7 @@ export function StudentStoriesPage() {
}; };
fetchStories(); fetchStories();
}, []); }, [statusFilter, sortBy]);
const filteredStories = stories.filter(story => const filteredStories = stories.filter(story =>
story.title.toLowerCase().includes(searchTerm.toLowerCase()) story.title.toLowerCase().includes(searchTerm.toLowerCase())
@ -184,10 +197,10 @@ export function StudentStoriesPage() {
className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden cursor-pointer hover:shadow-md transition" className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden cursor-pointer hover:shadow-md transition"
onClick={() => navigate(`/aluno/historias/${story.id}`)} onClick={() => navigate(`/aluno/historias/${story.id}`)}
> >
{story.cover && ( {story.content?.pages?.[0]?.image && (
<div className="relative aspect-video bg-gray-100"> <div className="relative aspect-video">
<img <img
src={`${story.cover.image_url}?width=400&quality=80&format=webp`} src={story.content.pages[0].image}
alt={story.title} alt={story.title}
className="w-full h-48 object-cover" className="w-full h-48 object-cover"
loading="lazy" loading="lazy"

View File

@ -1,158 +0,0 @@
import { createBrowserRouter } from 'react-router-dom';
import { RootLayout } from '../components/layout/RootLayout';
import { HomePage } from '../components/home/HomePage';
import { LoginForm } from '../components/auth/LoginForm';
import { SchoolRegistrationForm } from '../components/auth/SchoolRegistrationForm';
import { RegistrationForm } from '../components/RegistrationForm';
import { StoryViewer } from '../components/StoryViewer';
import { AuthCallback } from '../pages/AuthCallback';
import { DashboardLayout } from '../pages/dashboard/DashboardLayout';
import { DashboardHome } from '../pages/dashboard/DashboardHome';
import { ClassesPage } from '../pages/dashboard/classes/ClassesPage';
import { CreateClassPage } from '../pages/dashboard/classes/CreateClassPage';
import { TeachersPage } from '../pages/dashboard/teachers/TeachersPage';
import { InviteTeacherPage } from '../pages/dashboard/teachers/InviteTeacherPage';
import { StudentsPage } from '../pages/dashboard/students/StudentsPage';
import { AddStudentPage } from '../pages/dashboard/students/AddStudentPage';
import { DemoPage } from '../pages/demo/DemoPage';
import { StoryPage } from '../pages/story/StoryPage';
import { StudentSettingsPage } from '../pages/student-dashboard/StudentSettingsPage';
import { StudentDashboardLayout } from '../pages/student-dashboard/StudentDashboardLayout';
import { StudentDashboard, StudentClassPage } from '../pages/student-dashboard';
import { AchievementsPage } from '../pages/student-dashboard/AchievementsPage';
import React from 'react';
export const router = createBrowserRouter([
{
path: '/',
element: <RootLayout />,
children: [
{
index: true,
element: <HomePage />,
},
{
path: 'demo',
element: <StoryPage demo={true} />
},
{
path: 'login',
children: [
{
path: 'school',
element: <LoginForm userType="school" />,
},
{
path: 'teacher',
element: <LoginForm userType="teacher" />,
},
{
path: 'student',
element: <LoginForm userType="student" />,
}
]
},
{
path: 'register',
children: [
{
path: 'school',
element: <SchoolRegistrationForm />,
},
{
path: 'teacher',
element: <RegistrationForm
userType="teacher"
onComplete={(userData) => {
console.log(userData);
}}
/>,
}
]
},
{
path: 'dashboard',
element: <DashboardLayout />,
children: [
{
index: true,
element: <DashboardHome />,
},
{
path: 'turmas',
children: [
{
index: true,
element: <ClassesPage />,
},
{
path: 'nova',
element: <CreateClassPage />,
}
]
},
{
path: 'professores',
children: [
{
index: true,
element: <TeachersPage />,
},
{
path: 'convidar',
element: <InviteTeacherPage />,
}
]
},
{
path: 'alunos',
children: [
{
index: true,
element: <StudentsPage />,
},
{
path: 'novo',
element: <AddStudentPage />,
}
]
}
]
},
{
path: 'auth/callback',
element: <AuthCallback />
},
{
path: 'story/:storyId',
element: <StoryPage />
},
{
path: 'aluno',
element: <StudentDashboardLayout />,
children: [
{
index: true,
element: <StudentDashboard />,
},
{
path: 'configuracoes',
element: <StudentSettingsPage />,
},
{
path: 'conquistas',
element: <AchievementsPage />,
},
{
path: 'historias/:id',
element: <StoryPage />,
},
{
path: 'turmas/:classId',
element: <StudentClassPage />,
}
]
}
],
}
]);

View File

@ -105,6 +105,7 @@ export interface StoryPage {
} }
export interface Story { export interface Story {
cover: any;
id: string; id: string;
student_id: string; student_id: string;
class_id: string; class_id: string;