mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-18 14:27:51 +00:00
feat: adiciona novos recursos de formatação e tracking no editor
This commit is contained in:
parent
46e8ba0312
commit
ccbac66d28
30
CHANGELOG.md
30
CHANGELOG.md
@ -294,3 +294,33 @@ e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
|
|||||||
- Implementado feedback visual durante operações de salvamento
|
- Implementado feedback visual durante operações de salvamento
|
||||||
- Otimizado carregamento inicial da redação
|
- Otimizado carregamento inicial da redação
|
||||||
- Adicionado tratamento de estados para diferentes status da redação
|
- Adicionado tratamento de estados para diferentes status da redação
|
||||||
|
|
||||||
|
## [0.2.0] - 2024-03-21
|
||||||
|
|
||||||
|
### Adicionado
|
||||||
|
- Novos recursos de formatação no editor:
|
||||||
|
- Tachado (strike-through)
|
||||||
|
- Código inline
|
||||||
|
- Lista com marcadores
|
||||||
|
- Lista numerada
|
||||||
|
- Citação (blockquote)
|
||||||
|
- Rastreamento de eventos (tracking) em todos os botões do editor
|
||||||
|
|
||||||
|
### Modificado
|
||||||
|
- Melhorias no fluxo de redações:
|
||||||
|
- Carregamento correto do conteúdo após submissão para análise
|
||||||
|
- Salvamento automático do conteúdo antes da submissão
|
||||||
|
- Badge verde para status "analisada"
|
||||||
|
- Botão "Ver Análise" para redações analisadas
|
||||||
|
- Editor em modo somente leitura após submissão
|
||||||
|
- Contagem de palavras em todos os estados da redação
|
||||||
|
|
||||||
|
### Técnico
|
||||||
|
- Refatoração do componente Editor para incluir novos recursos de formatação
|
||||||
|
- Adição de trackingId em todos os botões para análise de uso
|
||||||
|
- Melhorias de acessibilidade com aria-labels em português
|
||||||
|
- Refatoração do EssayPage para incluir lógica de salvamento antes da submissão para análise
|
||||||
|
- Melhoria na query do Supabase para incluir conteúdo explicitamente
|
||||||
|
- Implementação de feedback visual durante operações de salvamento
|
||||||
|
- Otimização do carregamento inicial da redação
|
||||||
|
- Adição de tratamento de estado para diferentes status da redação
|
||||||
|
|||||||
@ -18,6 +18,11 @@ import {
|
|||||||
AlignCenter,
|
AlignCenter,
|
||||||
AlignRight,
|
AlignRight,
|
||||||
Highlighter,
|
Highlighter,
|
||||||
|
Strikethrough,
|
||||||
|
Code,
|
||||||
|
List,
|
||||||
|
ListOrdered,
|
||||||
|
Quote,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
|
||||||
interface EditorProps {
|
interface EditorProps {
|
||||||
@ -47,6 +52,7 @@ function MenuBar({ editor }: MenuBarProps) {
|
|||||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||||
className={cn(editor.isActive('bold') && 'bg-muted')}
|
className={cn(editor.isActive('bold') && 'bg-muted')}
|
||||||
aria-label="Negrito"
|
aria-label="Negrito"
|
||||||
|
trackingId="editor-bold-button"
|
||||||
>
|
>
|
||||||
<Bold className="h-4 w-4" />
|
<Bold className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -56,6 +62,7 @@ function MenuBar({ editor }: MenuBarProps) {
|
|||||||
onClick={() => editor.chain().focus().toggleItalic().run()}
|
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||||
className={cn(editor.isActive('italic') && 'bg-muted')}
|
className={cn(editor.isActive('italic') && 'bg-muted')}
|
||||||
aria-label="Itálico"
|
aria-label="Itálico"
|
||||||
|
trackingId="editor-italic-button"
|
||||||
>
|
>
|
||||||
<Italic className="h-4 w-4" />
|
<Italic className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -65,6 +72,7 @@ function MenuBar({ editor }: MenuBarProps) {
|
|||||||
onClick={() => editor.chain().focus().toggleUnderline().run()}
|
onClick={() => editor.chain().focus().toggleUnderline().run()}
|
||||||
className={cn(editor.isActive('underline') && 'bg-muted')}
|
className={cn(editor.isActive('underline') && 'bg-muted')}
|
||||||
aria-label="Sublinhado"
|
aria-label="Sublinhado"
|
||||||
|
trackingId="editor-underline-button"
|
||||||
>
|
>
|
||||||
<UnderlineIcon className="h-4 w-4" />
|
<UnderlineIcon className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -74,9 +82,60 @@ function MenuBar({ editor }: MenuBarProps) {
|
|||||||
onClick={() => editor.chain().focus().toggleHighlight().run()}
|
onClick={() => editor.chain().focus().toggleHighlight().run()}
|
||||||
className={cn(editor.isActive('highlight') && 'bg-muted')}
|
className={cn(editor.isActive('highlight') && 'bg-muted')}
|
||||||
aria-label="Destacar"
|
aria-label="Destacar"
|
||||||
|
trackingId="editor-highlight-button"
|
||||||
>
|
>
|
||||||
<Highlighter className="h-4 w-4" />
|
<Highlighter className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => editor.chain().focus().toggleStrike().run()}
|
||||||
|
className={cn(editor.isActive('strike') && 'bg-muted')}
|
||||||
|
aria-label="Tachado"
|
||||||
|
trackingId="editor-strike-button"
|
||||||
|
>
|
||||||
|
<Strikethrough className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => editor.chain().focus().toggleCode().run()}
|
||||||
|
className={cn(editor.isActive('code') && 'bg-muted')}
|
||||||
|
aria-label="Código"
|
||||||
|
trackingId="editor-code-button"
|
||||||
|
>
|
||||||
|
<Code className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||||
|
className={cn(editor.isActive('bulletList') && 'bg-muted')}
|
||||||
|
aria-label="Lista com marcadores"
|
||||||
|
trackingId="editor-bullet-list-button"
|
||||||
|
>
|
||||||
|
<List className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||||
|
className={cn(editor.isActive('orderedList') && 'bg-muted')}
|
||||||
|
aria-label="Lista numerada"
|
||||||
|
trackingId="editor-ordered-list-button"
|
||||||
|
>
|
||||||
|
<ListOrdered className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => editor.chain().focus().toggleBlockquote().run()}
|
||||||
|
className={cn(editor.isActive('blockquote') && 'bg-muted')}
|
||||||
|
aria-label="Citação"
|
||||||
|
trackingId="editor-blockquote-button"
|
||||||
|
>
|
||||||
|
<Quote className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
<div className="mx-2 w-[1px] bg-border" />
|
<div className="mx-2 w-[1px] bg-border" />
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@ -84,6 +143,7 @@ function MenuBar({ editor }: MenuBarProps) {
|
|||||||
onClick={() => editor.chain().focus().setTextAlign('left').run()}
|
onClick={() => editor.chain().focus().setTextAlign('left').run()}
|
||||||
className={cn(editor.isActive({ textAlign: 'left' }) && 'bg-muted')}
|
className={cn(editor.isActive({ textAlign: 'left' }) && 'bg-muted')}
|
||||||
aria-label="Alinhar à esquerda"
|
aria-label="Alinhar à esquerda"
|
||||||
|
trackingId="editor-align-left-button"
|
||||||
>
|
>
|
||||||
<AlignLeft className="h-4 w-4" />
|
<AlignLeft className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -93,6 +153,7 @@ function MenuBar({ editor }: MenuBarProps) {
|
|||||||
onClick={() => editor.chain().focus().setTextAlign('center').run()}
|
onClick={() => editor.chain().focus().setTextAlign('center').run()}
|
||||||
className={cn(editor.isActive({ textAlign: 'center' }) && 'bg-muted')}
|
className={cn(editor.isActive({ textAlign: 'center' }) && 'bg-muted')}
|
||||||
aria-label="Centralizar"
|
aria-label="Centralizar"
|
||||||
|
trackingId="editor-align-center-button"
|
||||||
>
|
>
|
||||||
<AlignCenter className="h-4 w-4" />
|
<AlignCenter className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -102,6 +163,7 @@ function MenuBar({ editor }: MenuBarProps) {
|
|||||||
onClick={() => editor.chain().focus().setTextAlign('right').run()}
|
onClick={() => editor.chain().focus().setTextAlign('right').run()}
|
||||||
className={cn(editor.isActive({ textAlign: 'right' }) && 'bg-muted')}
|
className={cn(editor.isActive({ textAlign: 'right' }) && 'bg-muted')}
|
||||||
aria-label="Alinhar à direita"
|
aria-label="Alinhar à direita"
|
||||||
|
trackingId="editor-align-right-button"
|
||||||
>
|
>
|
||||||
<AlignRight className="h-4 w-4" />
|
<AlignRight className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -124,7 +186,6 @@ export function Editor({
|
|||||||
heading: false,
|
heading: false,
|
||||||
codeBlock: false,
|
codeBlock: false,
|
||||||
horizontalRule: false,
|
horizontalRule: false,
|
||||||
table: false,
|
|
||||||
}),
|
}),
|
||||||
Placeholder.configure({
|
Placeholder.configure({
|
||||||
placeholder,
|
placeholder,
|
||||||
|
|||||||
@ -3,9 +3,8 @@ import { useParams, useNavigate } from 'react-router-dom';
|
|||||||
import { supabase } from '@/lib/supabase';
|
import { supabase } from '@/lib/supabase';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { ArrowLeft, CheckCircle2, XCircle, Loader2 } from 'lucide-react';
|
import { ArrowLeft, CheckCircle2, XCircle } from 'lucide-react';
|
||||||
import { Progress } from '@/components/ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
|
|
||||||
interface EssayAnalysis {
|
interface EssayAnalysis {
|
||||||
id: string;
|
id: string;
|
||||||
@ -73,50 +72,14 @@ export function EssayAnalysis() {
|
|||||||
// Carregar análise
|
// Carregar análise
|
||||||
const { data: analysisData, error: analysisError } = await supabase
|
const { data: analysisData, error: analysisError } = await supabase
|
||||||
.from('essay_analyses')
|
.from('essay_analyses')
|
||||||
.select(`
|
.select('*')
|
||||||
*,
|
|
||||||
feedback:essay_analysis_feedback(
|
|
||||||
structure_feedback,
|
|
||||||
content_feedback,
|
|
||||||
language_feedback
|
|
||||||
),
|
|
||||||
strengths:essay_analysis_strengths(strength),
|
|
||||||
improvements:essay_analysis_improvements(improvement),
|
|
||||||
scores:essay_analysis_scores(
|
|
||||||
adequacy,
|
|
||||||
coherence,
|
|
||||||
cohesion,
|
|
||||||
vocabulary,
|
|
||||||
grammar
|
|
||||||
)
|
|
||||||
`)
|
|
||||||
.eq('essay_id', id)
|
.eq('essay_id', id)
|
||||||
.order('created_at', { ascending: false })
|
.order('created_at', { ascending: false })
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (analysisError) throw analysisError;
|
if (analysisError) throw analysisError;
|
||||||
|
setAnalysis(analysisData);
|
||||||
// Transformar os dados para o formato esperado
|
|
||||||
const formattedAnalysis = {
|
|
||||||
...analysisData,
|
|
||||||
feedback: {
|
|
||||||
structure: analysisData.feedback[0]?.structure_feedback || '',
|
|
||||||
content: analysisData.feedback[0]?.content_feedback || '',
|
|
||||||
language: analysisData.feedback[0]?.language_feedback || ''
|
|
||||||
},
|
|
||||||
strengths: analysisData.strengths?.map(s => s.strength) || [],
|
|
||||||
improvements: analysisData.improvements?.map(i => i.improvement) || [],
|
|
||||||
criteria_scores: {
|
|
||||||
adequacy: analysisData.scores[0]?.adequacy || 0,
|
|
||||||
coherence: analysisData.scores[0]?.coherence || 0,
|
|
||||||
cohesion: analysisData.scores[0]?.cohesion || 0,
|
|
||||||
vocabulary: analysisData.scores[0]?.vocabulary || 0,
|
|
||||||
grammar: analysisData.scores[0]?.grammar || 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setAnalysis(formattedAnalysis);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao carregar dados:', error);
|
console.error('Erro ao carregar dados:', error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -124,177 +87,160 @@ export function EssayAnalysis() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) return <div>Carregando...</div>;
|
||||||
return (
|
if (!essay || !analysis) return <div>Análise não encontrada</div>;
|
||||||
<div className="container mx-auto p-6">
|
|
||||||
<div className="animate-pulse space-y-6">
|
|
||||||
<div className="h-8 w-48 bg-gray-200 rounded" />
|
|
||||||
<div className="h-96 bg-gray-200 rounded-xl" />
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
||||||
{[1, 2, 3].map((i) => (
|
|
||||||
<div key={i} className="h-48 bg-gray-200 rounded-xl" />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!essay || !analysis) {
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto p-6">
|
<div className="container mx-auto p-6">
|
||||||
<div className="text-center py-12">
|
<div className="flex items-center gap-4 mb-6">
|
||||||
<div className="text-red-500 mb-4">Análise não encontrada</div>
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => navigate('/aluno/redacoes')}
|
onClick={() => navigate('/aluno/redacoes')}
|
||||||
className="text-purple-600 hover:text-purple-700"
|
className="text-purple-600 hover:text-purple-700"
|
||||||
|
trackingId="essay-analysis-back-to-list-button"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
Voltar para redações
|
Voltar para lista de redações
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="container mx-auto p-6">
|
|
||||||
{/* Cabeçalho */}
|
|
||||||
<div className="flex items-center justify-between mb-8">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => navigate(`/aluno/redacoes/${id}`)}
|
onClick={() => navigate(`/aluno/redacoes/${id}`)}
|
||||||
className="text-gray-600 hover:text-gray-900"
|
className="text-gray-600 hover:text-gray-900"
|
||||||
|
trackingId="essay-analysis-back-to-essay-button"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
Voltar para redação
|
Voltar para redação
|
||||||
</Button>
|
</Button>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">{essay.title}</h1>
|
<h1 className="text-3xl font-bold">{essay.title}</h1>
|
||||||
<p className="text-gray-500">
|
<p className="text-muted-foreground">
|
||||||
{essay.essay_type.title} • {essay.essay_genre.title}
|
{essay.essay_type.title} • {essay.essay_genre.title}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Conteúdo Principal */}
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
<div className="space-y-8">
|
{/* Pontuação Geral */}
|
||||||
{/* Cartão de Pontuação */}
|
<Card>
|
||||||
<Card className="bg-gradient-to-br from-purple-50 to-white border-purple-100">
|
<CardHeader>
|
||||||
<CardContent className="pt-6">
|
<CardTitle>Pontuação Geral</CardTitle>
|
||||||
<div className="flex flex-col items-center justify-center">
|
</CardHeader>
|
||||||
<div className="flex items-center mb-2">
|
<CardContent>
|
||||||
<span className="text-6xl font-bold text-purple-600">{analysis.overall_score}</span>
|
<div className="flex items-center justify-center">
|
||||||
<span className="text-2xl text-purple-600 ml-2">/100</span>
|
<div className="text-6xl font-bold text-primary">
|
||||||
|
{analysis.overall_score}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-gray-500">Pontuação Geral</div>
|
<div className="text-2xl ml-1">/100</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Grid de Métricas */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
||||||
{/* Pontos Fortes */}
|
{/* Pontos Fortes */}
|
||||||
<Card className="bg-gradient-to-br from-green-50 to-white border-green-100">
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2 text-green-700">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<CheckCircle2 className="h-5 w-5" />
|
<CheckCircle2 className="h-5 w-5 text-success" />
|
||||||
Pontos Fortes
|
Pontos Fortes
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<ul className="space-y-2">
|
<ul className="list-disc list-inside space-y-2">
|
||||||
{analysis.strengths.map((strength, index) => (
|
{analysis.strengths.map((strength, index) => (
|
||||||
<li key={index} className="flex items-start gap-2 text-green-700">
|
<li key={index} className="text-success">{strength}</li>
|
||||||
<div className="w-1.5 h-1.5 rounded-full bg-green-500 mt-2" />
|
|
||||||
<span>{strength}</span>
|
|
||||||
</li>
|
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Pontos a Melhorar */}
|
{/* Pontos a Melhorar */}
|
||||||
<Card className="bg-gradient-to-br from-amber-50 to-white border-amber-100">
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2 text-amber-700">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<XCircle className="h-5 w-5" />
|
<XCircle className="h-5 w-5 text-destructive" />
|
||||||
Pontos a Melhorar
|
Pontos a Melhorar
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<ul className="space-y-2">
|
<ul className="list-disc list-inside space-y-2">
|
||||||
{analysis.improvements.map((improvement, index) => (
|
{analysis.improvements.map((improvement, index) => (
|
||||||
<li key={index} className="flex items-start gap-2 text-amber-700">
|
<li key={index} className="text-destructive">{improvement}</li>
|
||||||
<div className="w-1.5 h-1.5 rounded-full bg-amber-500 mt-2" />
|
|
||||||
<span>{improvement}</span>
|
|
||||||
</li>
|
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Feedback Detalhado */}
|
||||||
|
<Card className="md:col-span-2">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Feedback Detalhado</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold mb-2">Estrutura</h4>
|
||||||
|
<p className="text-muted-foreground">{analysis.feedback.structure}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold mb-2">Conteúdo</h4>
|
||||||
|
<p className="text-muted-foreground">{analysis.feedback.content}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold mb-2">Linguagem</h4>
|
||||||
|
<p className="text-muted-foreground">{analysis.feedback.language}</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Critérios de Avaliação */}
|
{/* Critérios de Avaliação */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Critérios de Avaliação</CardTitle>
|
<CardTitle>Critérios de Avaliação</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{[
|
<div>
|
||||||
{ label: 'Adequação ao Gênero', value: analysis.criteria_scores.adequacy, color: 'bg-blue-500' },
|
<div className="flex justify-between mb-1">
|
||||||
{ label: 'Coerência', value: analysis.criteria_scores.coherence, color: 'bg-green-500' },
|
<span>Adequação ao Gênero</span>
|
||||||
{ label: 'Coesão', value: analysis.criteria_scores.cohesion, color: 'bg-purple-500' },
|
<span>{analysis.criteria_scores.adequacy}%</span>
|
||||||
{ label: 'Vocabulário', value: analysis.criteria_scores.vocabulary, color: 'bg-orange-500' },
|
|
||||||
{ label: 'Gramática', value: analysis.criteria_scores.grammar, color: 'bg-pink-500' }
|
|
||||||
].map((criterion) => (
|
|
||||||
<div key={criterion.label}>
|
|
||||||
<div className="flex justify-between mb-1 text-sm">
|
|
||||||
<span className="text-gray-600">{criterion.label}</span>
|
|
||||||
<span className="font-medium">{criterion.value}%</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="h-2 rounded-full bg-gray-100">
|
<Progress value={analysis.criteria_scores.adequacy} />
|
||||||
<div
|
|
||||||
className={cn("h-full rounded-full transition-all", criterion.color)}
|
|
||||||
style={{ width: `${criterion.value}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between mb-1">
|
||||||
|
<span>Coerência</span>
|
||||||
|
<span>{analysis.criteria_scores.coherence}%</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
<Progress value={analysis.criteria_scores.coherence} />
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
{/* Feedback Detalhado */}
|
<div className="flex justify-between mb-1">
|
||||||
<Card>
|
<span>Coesão</span>
|
||||||
<CardHeader>
|
<span>{analysis.criteria_scores.cohesion}%</span>
|
||||||
<CardTitle>Feedback Detalhado</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h4 className="font-semibold text-gray-900">Estrutura</h4>
|
|
||||||
<p className="text-gray-600">{analysis.feedback.structure}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<Progress value={analysis.criteria_scores.cohesion} />
|
||||||
<h4 className="font-semibold text-gray-900">Conteúdo</h4>
|
|
||||||
<p className="text-gray-600">{analysis.feedback.content}</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div>
|
||||||
<h4 className="font-semibold text-gray-900">Linguagem</h4>
|
<div className="flex justify-between mb-1">
|
||||||
<p className="text-gray-600">{analysis.feedback.language}</p>
|
<span>Vocabulário</span>
|
||||||
|
<span>{analysis.criteria_scores.vocabulary}%</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={analysis.criteria_scores.vocabulary} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between mb-1">
|
||||||
|
<span>Gramática</span>
|
||||||
|
<span>{analysis.criteria_scores.grammar}%</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={analysis.criteria_scores.grammar} />
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Sugestões */}
|
{/* Sugestões */}
|
||||||
<Card className="bg-gradient-to-br from-blue-50 to-white border-blue-100">
|
<Card className="md:col-span-3">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-blue-700">Sugestões para Melhoria</CardTitle>
|
<CardTitle>Sugestões para Melhoria</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<p className="text-blue-700 whitespace-pre-line">
|
<p className="text-muted-foreground whitespace-pre-line">
|
||||||
{analysis.suggestions}
|
{analysis.suggestions}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user