mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-17 05:47:52 +00:00
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:
parent
fbeeace8bb
commit
7087a87ece
@ -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
|
||||||
|
|||||||
@ -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]);
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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 />,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
@ -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;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user