[new/parecer juridico]

This commit is contained in:
luisfepsale
2025-11-19 15:19:41 -03:00
parent 0fa382ef0e
commit a28b780718
3 changed files with 152 additions and 34 deletions
+77 -12
View File
@@ -1,7 +1,8 @@
import { useState, useEffect } from "react";
import { Plus, Search, Eye, Trash2, ArrowUpDown, Download } from "lucide-react";
import { useState, useEffect, useCallback } from "react";
import { Plus, Search, Eye, Trash2, ArrowUpDown, Download, Loader2, CheckCircle2, XCircle } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import {
Table,
TableBody,
@@ -43,6 +44,7 @@ type SortOrder = "asc" | "desc";
export const AgentView = () => {
const [opinions, setOpinions] = useState<OpinionRecord[]>([]);
const [pendingOpinions, setPendingOpinions] = useState<OpinionRecord[]>([]); // Registros temporários sendo processados
const [searchTerm, setSearchTerm] = useState("");
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(10);
@@ -57,7 +59,7 @@ export const AgentView = () => {
const { toast } = useToast();
// Carrega pareceres da API
const loadOpinions = async () => {
const loadOpinions = useCallback(async () => {
setIsLoading(true);
try {
const data = await agentService.getOpinions({
@@ -65,7 +67,24 @@ export const AgentView = () => {
per_page: itemsPerPage,
search: searchTerm,
});
setOpinions(data);
// Adiciona status aos pareceres da API
const opinionsWithStatus = data.map(opinion => ({
...opinion,
status: (opinion.file_url || opinion.file_url_melhoria) ? 'concluido' : 'processando' as const,
}));
setOpinions(opinionsWithStatus);
// Remove registros temporários que agora estão na API
setPendingOpinions(prev =>
prev.filter(pending =>
!opinionsWithStatus.some(opinion =>
opinion.titulo === pending.titulo &&
opinion.created_at.substring(0, 10) === pending.created_at.substring(0, 10)
)
)
);
} catch (error: any) {
console.error('Erro ao carregar pareceres:', error);
toast({
@@ -76,12 +95,23 @@ export const AgentView = () => {
} finally {
setIsLoading(false);
}
};
}, [currentPage, itemsPerPage, searchTerm, toast]);
// Carrega pareceres ao montar o componente e quando mudar página/busca
// Polling automático para atualizar status dos pareceres
useEffect(() => {
// Carrega imediatamente
loadOpinions();
}, [currentPage, itemsPerPage]);
// Configura polling a cada 10 segundos se houver pareceres pendentes
const interval = setInterval(() => {
if (pendingOpinions.length > 0) {
console.log('Polling: Atualizando lista de pareceres...');
loadOpinions();
}
}, 10000); // 10 segundos
return () => clearInterval(interval);
}, [currentPage, itemsPerPage, pendingOpinions.length, loadOpinions]);
// Debounce para busca
useEffect(() => {
@@ -94,7 +124,7 @@ export const AgentView = () => {
}, 500);
return () => clearTimeout(timer);
}, [searchTerm]);
}, [searchTerm, loadOpinions]);
const handleSort = (field: SortField) => {
if (sortField === field) {
@@ -105,7 +135,10 @@ export const AgentView = () => {
}
};
const sortedOpinions = [...opinions].sort((a, b) => {
// Mescla pareceres da API com registros temporários
const allOpinions = [...pendingOpinions, ...opinions];
const sortedOpinions = [...allOpinions].sort((a, b) => {
const multiplier = sortOrder === "asc" ? 1 : -1;
if (sortField === "created_at") {
@@ -119,8 +152,13 @@ export const AgentView = () => {
const totalPages = Math.ceil(sortedOpinions.length / itemsPerPage);
// Callback quando um parecer está sendo criado (registro temporário)
const handleOpinionCreating = (tempOpinion: OpinionRecord) => {
setPendingOpinions(prev => [tempOpinion, ...prev]);
};
// Callback quando um parecer foi criado (recarrega da API)
const handleOpinionCreated = () => {
// Recarrega a lista após criar um novo parecer
loadOpinions();
};
@@ -284,6 +322,9 @@ export const AgentView = () => {
<ArrowUpDown className="w-4 h-4" />
</Button>
</TableHead>
<TableHead className="hidden lg:table-cell text-center">
<span className="font-semibold text-xs md:text-sm">Status</span>
</TableHead>
<TableHead className="hidden sm:table-cell">
<Button
variant="ghost"
@@ -300,13 +341,13 @@ export const AgentView = () => {
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={4} className="text-center py-12 text-muted-foreground">
<TableCell colSpan={5} className="text-center py-12 text-muted-foreground">
Carregando pareceres...
</TableCell>
</TableRow>
) : sortedOpinions.length === 0 ? (
<TableRow>
<TableCell colSpan={4} className="text-center py-12 text-muted-foreground">
<TableCell colSpan={5} className="text-center py-12 text-muted-foreground">
{searchTerm
? "Nenhum parecer encontrado"
: "Nenhum parecer criado ainda. Clique em 'Novo Parecer' para começar."}
@@ -317,6 +358,26 @@ export const AgentView = () => {
<TableRow key={opinion.id}>
<TableCell className="font-medium text-xs md:text-sm">{opinion.titulo}</TableCell>
<TableCell className="hidden md:table-cell text-sm">{opinion.categoria || "-"}</TableCell>
<TableCell className="hidden lg:table-cell text-center">
{opinion.status === 'processando' && (
<Badge variant="secondary" className="gap-1 bg-yellow-100 text-yellow-800 hover:bg-yellow-100">
<Loader2 className="w-3 h-3 animate-spin" />
Gerando...
</Badge>
)}
{opinion.status === 'concluido' && (
<Badge variant="secondary" className="gap-1 bg-green-100 text-green-800 hover:bg-green-100">
<CheckCircle2 className="w-3 h-3" />
Concluído
</Badge>
)}
{opinion.status === 'erro' && (
<Badge variant="destructive" className="gap-1">
<XCircle className="w-3 h-3" />
Erro
</Badge>
)}
</TableCell>
<TableCell className="hidden sm:table-cell text-xs md:text-sm">
{new Date(opinion.created_at).toLocaleDateString("pt-BR")}
</TableCell>
@@ -328,6 +389,7 @@ export const AgentView = () => {
onClick={() => handleViewOpinion(opinion)}
title="Visualizar"
className="h-7 w-7 md:h-9 md:w-9"
disabled={opinion.isLocalPending || opinion.status === 'processando'}
>
<Eye className="w-3 h-3 md:w-4 md:h-4" />
</Button>
@@ -338,6 +400,7 @@ export const AgentView = () => {
size="icon"
title="Baixar"
className="h-7 w-7 md:h-9 md:w-9"
disabled={opinion.isLocalPending || opinion.status === 'processando'}
>
<Download className="w-3 h-3 md:w-4 md:h-4" />
</Button>
@@ -365,6 +428,7 @@ export const AgentView = () => {
onClick={() => setOpinionToDelete(opinion)}
title="Excluir"
className="h-7 w-7 md:h-9 md:w-9 text-destructive hover:text-destructive"
disabled={opinion.isLocalPending}
>
<Trash2 className="w-3 h-3 md:w-4 md:h-4" />
</Button>
@@ -438,6 +502,7 @@ export const AgentView = () => {
onOpenChange={setIsDialogOpen}
selectedOpinion={selectedOpinion}
onOpinionCreated={handleOpinionCreated}
onOpinionCreating={handleOpinionCreating}
/>
<AlertDialog open={!!opinionToDelete} onOpenChange={(open) => !open && setOpinionToDelete(null)}>
+64 -21
View File
@@ -13,6 +13,7 @@ import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useToast } from "@/hooks/use-toast";
import { ToastAction } from "@/components/ui/toast";
import { agentService, OpinionRecord } from "@/services/agent";
interface OpinionDialogProps {
@@ -20,6 +21,7 @@ interface OpinionDialogProps {
onOpenChange: (open: boolean) => void;
selectedOpinion: OpinionRecord | null;
onOpinionCreated: () => void;
onOpinionCreating?: (tempOpinion: OpinionRecord) => void; // Callback para adicionar registro temporário
}
export const OpinionDialog = ({
@@ -27,6 +29,7 @@ export const OpinionDialog = ({
onOpenChange,
selectedOpinion,
onOpinionCreated,
onOpinionCreating,
}: OpinionDialogProps) => {
const [title, setTitle] = useState("");
const [category, setCategory] = useState("");
@@ -68,46 +71,86 @@ export const OpinionDialog = ({
return;
}
setIsGenerating(true);
// Criar registro temporário para adicionar na tabela imediatamente
const tempId = `temp-${Date.now()}`;
const tempOpinion: OpinionRecord = {
id: tempId,
estabelecimento_id: 0,
user_email: '',
titulo: title,
categoria: category,
instrucoes: instructions,
file_url: '',
file_url_melhoria: '',
created_at: new Date().toISOString(),
status: 'processando',
isLocalPending: true,
};
// Adiciona o registro temporário na tabela
if (onOpinionCreating) {
onOpinionCreating(tempOpinion);
}
// Criar toast de progresso que não fecha automaticamente
const loadingToast = toast({
title: "Gerando parecer jurídico...",
description: "O parecer está sendo processado. Isso pode levar alguns minutos.",
duration: Infinity, // Toast não fecha automaticamente
});
// Fecha o dialog imediatamente
setIsGenerating(false);
onOpenChange(false);
// Limpa os campos
setTitle("");
setCategory("");
setInstructions("");
setCreatedOpinion(null);
// Executar requisição em background
try {
const response = await agentService.createOpinion({
titulo: title,
categoria: category,
instrucoes: instructions,
titulo: tempOpinion.titulo,
categoria: tempOpinion.categoria,
instrucoes: tempOpinion.instrucoes,
});
if (response.success) {
toast({
title: "Parecer gerado com sucesso!",
description: "O parecer foi criado e está disponível para download.",
});
// Chama o callback para atualizar a lista
// Recarrega a lista para pegar o parecer real da API
onOpinionCreated();
// Fecha o dialog após sucesso
setIsGenerating(false);
onOpenChange(false);
// Limpa os campos
setTitle("");
setCategory("");
setInstructions("");
setCreatedOpinion(null);
// Atualizar toast para sucesso
loadingToast.update({
id: loadingToast.id,
title: "Parecer gerado com sucesso!",
description: "O parecer foi criado e está disponível para download.",
duration: 8000, // Toast fecha após 8 segundos
action: (
<ToastAction altText="Fechar notificação" onClick={() => loadingToast.dismiss()}>
OK
</ToastAction>
),
});
} else {
throw new Error('Falha ao criar parecer');
}
} catch (error: any) {
console.error("Erro ao gerar parecer:", error);
setIsGenerating(false);
const errorMessage = error.message || "Não foi possível gerar o parecer. Tente novamente.";
toast({
// Recarrega a lista para remover o registro temporário e tentar pegar dados reais
onOpinionCreated();
// Atualizar toast para erro
loadingToast.update({
id: loadingToast.id,
title: "Erro ao gerar parecer",
description: errorMessage,
variant: "destructive",
duration: 10000, // Toast de erro fecha após 10 segundos
});
}
};