mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-16 21:37:51 +00:00
style: padroniza visual do editor de redações
- Alinha estilo com StoryPage e CreateStoryPage - Adiciona suporte a texto adaptativo - Melhora feedback visual e estados interativos - Implementa loading states animados
This commit is contained in:
parent
e9005e429f
commit
1bc307d599
@ -17,6 +17,11 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { useSession } from '@/hooks/useSession';
|
||||
import { useUppercasePreference } from '@/hooks/useUppercasePreference';
|
||||
import { AdaptiveText, AdaptiveTitle, AdaptiveParagraph } from '@/components/ui/adaptive-text';
|
||||
import { TextCaseToggle } from '@/components/ui/text-case-toggle';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface Essay {
|
||||
id: string;
|
||||
@ -48,6 +53,8 @@ export function EssayPage() {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [wordCount, setWordCount] = useState(0);
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const { session } = useSession();
|
||||
const { isUpperCase, toggleUppercase, isLoading: isUppercaseLoading } = useUppercasePreference(session?.user?.id);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
@ -157,136 +164,205 @@ export function EssayPage() {
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => navigate('/aluno/redacoes')}
|
||||
trackingId="essay-back-to-list-button"
|
||||
trackingProperties={{
|
||||
action: 'back_to_essays_list',
|
||||
page: 'essay_editor'
|
||||
}}
|
||||
>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Voltar
|
||||
</Button>
|
||||
<div>
|
||||
<Input
|
||||
value={essay.title}
|
||||
onChange={(e) => setEssay({ ...essay, title: e.target.value })}
|
||||
className="text-2xl font-bold border-none focus:border-none"
|
||||
placeholder="Título da redação"
|
||||
/>
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span>{essay.essay_type.title}</span>
|
||||
<span>•</span>
|
||||
<span>{essay.essay_genre.title}</span>
|
||||
<span>•</span>
|
||||
<Badge variant={essay.status === 'draft' ? 'secondary' : 'default'}>
|
||||
{essay.status === 'draft' ? 'Rascunho' : 'Enviada'}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={saveEssay}
|
||||
disabled={saving}
|
||||
trackingId="essay-save-button"
|
||||
trackingProperties={{
|
||||
action: 'save_essay',
|
||||
page: 'essay_editor',
|
||||
status: essay.status
|
||||
}}
|
||||
>
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
{saving ? 'Salvando...' : 'Salvar'}
|
||||
</Button>
|
||||
{essay.status === 'draft' && (
|
||||
<>
|
||||
<Button
|
||||
onClick={submitForAnalysis}
|
||||
disabled={!isWithinWordLimit}
|
||||
title={!isWithinWordLimit ? 'Número de palavras fora do limite' : ''}
|
||||
trackingId="essay-submit-analysis-button"
|
||||
trackingProperties={{
|
||||
action: 'submit_for_analysis',
|
||||
page: 'essay_editor',
|
||||
word_count: wordCount,
|
||||
within_limit: isWithinWordLimit
|
||||
}}
|
||||
>
|
||||
<Send className="mr-2 h-4 w-4" />
|
||||
Enviar para análise
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => setShowDeleteDialog(true)}
|
||||
trackingId="essay-delete-button"
|
||||
trackingProperties={{
|
||||
action: 'delete_essay',
|
||||
page: 'essay_editor',
|
||||
status: essay.status
|
||||
}}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Deletar
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => navigate('/aluno/redacoes')}
|
||||
className="text-gray-600 hover:text-gray-900"
|
||||
trackingId="essay-back-to-list-button"
|
||||
trackingProperties={{
|
||||
action: 'back_to_essays_list',
|
||||
page: 'essay_editor'
|
||||
}}
|
||||
>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
<AdaptiveText text="Voltar para redações" isUpperCase={isUpperCase} />
|
||||
</Button>
|
||||
<TextCaseToggle
|
||||
isUpperCase={isUpperCase}
|
||||
onToggle={toggleUppercase}
|
||||
isLoading={isUppercaseLoading}
|
||||
className="ml-auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<div className="md:col-span-3">
|
||||
<Textarea
|
||||
value={essay.content}
|
||||
onChange={(e) => setEssay({ ...essay, content: e.target.value })}
|
||||
className="min-h-[500px] font-mono"
|
||||
placeholder="Escreva sua redação aqui..."
|
||||
/>
|
||||
<div className="mt-2 text-sm text-muted-foreground">
|
||||
{wordCount} palavras
|
||||
{!isWithinWordLimit && (
|
||||
<span className="text-destructive">
|
||||
{' '}(mínimo: {essay.essay_genre.requirements.min_words},
|
||||
máximo: {essay.essay_genre.requirements.max_words})
|
||||
</span>
|
||||
)}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 space-y-6">
|
||||
{loading ? (
|
||||
<div className="animate-pulse space-y-4">
|
||||
<div className="h-8 bg-gray-100 rounded w-1/3" />
|
||||
<div className="h-4 bg-gray-100 rounded w-1/4" />
|
||||
<div className="h-64 bg-gray-100 rounded mt-8" />
|
||||
</div>
|
||||
</div>
|
||||
) : !essay ? (
|
||||
<div className="text-center py-12">
|
||||
<AdaptiveText
|
||||
text="Redação não encontrada"
|
||||
isUpperCase={isUpperCase}
|
||||
className="text-gray-600"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
value={essay.title}
|
||||
onChange={(e) => setEssay({ ...essay, title: e.target.value })}
|
||||
className="text-2xl font-bold border-none focus:border-none bg-transparent px-0"
|
||||
placeholder="Título da redação"
|
||||
/>
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600 mt-1">
|
||||
<AdaptiveText text={essay.essay_type?.title} isUpperCase={isUpperCase} />
|
||||
<span>•</span>
|
||||
<AdaptiveText text={essay.essay_genre?.title} isUpperCase={isUpperCase} />
|
||||
<span>•</span>
|
||||
<Badge variant={essay.status === 'draft' ? 'secondary' : 'default'}>
|
||||
<AdaptiveText
|
||||
text={essay.status === 'draft' ? 'Rascunho' : 'Enviada'}
|
||||
isUpperCase={isUpperCase}
|
||||
/>
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<h3 className="font-semibold mb-2">Requisitos do Gênero</h3>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<p>Mínimo: {essay.essay_genre.requirements.min_words} palavras</p>
|
||||
<p>Máximo: {essay.essay_genre.requirements.max_words} palavras</p>
|
||||
<p className="mt-2">Elementos necessários:</p>
|
||||
<ul className="list-disc list-inside">
|
||||
{essay.essay_genre.requirements.required_elements.map((element, index) => (
|
||||
<li key={index}>{element}</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={saveEssay}
|
||||
disabled={saving}
|
||||
trackingId="essay-save-button"
|
||||
trackingProperties={{
|
||||
action: 'save_essay',
|
||||
page: 'essay_editor',
|
||||
status: essay.status
|
||||
}}
|
||||
>
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
<AdaptiveText
|
||||
text={saving ? 'Salvando...' : 'Salvar'}
|
||||
isUpperCase={isUpperCase}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
{essay.status === 'draft' && (
|
||||
<>
|
||||
<Button
|
||||
onClick={submitForAnalysis}
|
||||
disabled={!isWithinWordLimit}
|
||||
title={!isWithinWordLimit ? 'Número de palavras fora do limite' : ''}
|
||||
trackingId="essay-submit-analysis-button"
|
||||
trackingProperties={{
|
||||
action: 'submit_for_analysis',
|
||||
page: 'essay_editor',
|
||||
word_count: wordCount,
|
||||
within_limit: isWithinWordLimit
|
||||
}}
|
||||
>
|
||||
<Send className="mr-2 h-4 w-4" />
|
||||
<AdaptiveText text="Enviar para análise" isUpperCase={isUpperCase} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => setShowDeleteDialog(true)}
|
||||
trackingId="essay-delete-button"
|
||||
trackingProperties={{
|
||||
action: 'delete_essay',
|
||||
page: 'essay_editor',
|
||||
status: essay.status
|
||||
}}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
<AdaptiveText text="Deletar" isUpperCase={isUpperCase} />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<div className="md:col-span-3">
|
||||
<Textarea
|
||||
value={essay.content}
|
||||
onChange={(e) => setEssay({ ...essay, content: e.target.value })}
|
||||
className="min-h-[500px] font-mono resize-none p-4"
|
||||
placeholder="Escreva sua redação aqui..."
|
||||
/>
|
||||
<div className="mt-2 text-sm">
|
||||
<span className={cn(
|
||||
"font-medium",
|
||||
isWithinWordLimit ? "text-gray-600" : "text-red-600"
|
||||
)}>
|
||||
{wordCount} palavras
|
||||
</span>
|
||||
{!isWithinWordLimit && (
|
||||
<span className="text-red-600">
|
||||
{' '}(mínimo: {essay.essay_genre.requirements.min_words},
|
||||
máximo: {essay.essay_genre.requirements.max_words})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card className="h-fit">
|
||||
<CardContent className="p-4">
|
||||
<h3 className="font-semibold mb-4">
|
||||
<AdaptiveText text="Requisitos do Gênero" isUpperCase={isUpperCase} />
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="bg-purple-50 p-4 rounded-lg border border-purple-100">
|
||||
<div className="space-y-2 text-sm text-purple-800">
|
||||
<p>
|
||||
<AdaptiveText
|
||||
text={`Mínimo: ${essay.essay_genre.requirements.min_words} palavras`}
|
||||
isUpperCase={isUpperCase}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<AdaptiveText
|
||||
text={`Máximo: ${essay.essay_genre.requirements.max_words} palavras`}
|
||||
isUpperCase={isUpperCase}
|
||||
/>
|
||||
</p>
|
||||
<p className="mt-2 font-medium">
|
||||
<AdaptiveText text="Elementos necessários:" isUpperCase={isUpperCase} />
|
||||
</p>
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
{essay.essay_genre.requirements.required_elements.map((element, index) => (
|
||||
<li key={index}>
|
||||
<AdaptiveText text={element} isUpperCase={isUpperCase} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Você tem certeza?</AlertDialogTitle>
|
||||
<AlertDialogTitle>
|
||||
<AdaptiveText text="Você tem certeza?" isUpperCase={isUpperCase} />
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Esta ação não pode ser desfeita. Isso excluirá permanentemente sua redação.
|
||||
<AdaptiveText
|
||||
text="Esta ação não pode ser desfeita. Isso excluirá permanentemente sua redação."
|
||||
isUpperCase={isUpperCase}
|
||||
/>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
||||
<AlertDialogCancel>
|
||||
<AdaptiveText text="Cancelar" isUpperCase={isUpperCase} />
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={deleteEssay} className="bg-red-600 hover:bg-red-700">
|
||||
Deletar
|
||||
<AdaptiveText text="Deletar" isUpperCase={isUpperCase} />
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user