[Parecer Juridico]

This commit is contained in:
luisfepsale
2025-10-28 09:56:35 -03:00
parent 9f75feb69a
commit 45ea53b364
8 changed files with 904 additions and 225 deletions
+67 -68
View File
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import { Search, FileText } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
@@ -12,77 +12,70 @@ import {
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
import { LegalOpinion } from "./AgentView";
import { agentService, OpinionRecord } from "@/services/agent";
import { useToast } from "@/hooks/use-toast";
interface AgentSearchProps {
onSelectOpinion: (opinion: LegalOpinion) => void;
onSelectOpinion: (opinion: OpinionRecord) => void;
}
// Simulação de base de pareceres
const mockOpinionsDatabase: LegalOpinion[] = [
{
id: "base-1",
title: "Análise Contratual - Prestação de Serviços Continuados",
content: "Parecer completo sobre prestação de serviços...",
createdAt: new Date("2024-01-15"),
category: "Direito Civil",
},
{
id: "base-2",
title: "Rescisão de Contrato de Trabalho - Justa Causa",
content: "Análise jurídica sobre rescisão contratual...",
createdAt: new Date("2024-02-20"),
category: "Direito Trabalhista",
},
{
id: "base-3",
title: "Responsabilidade Civil - Acidente de Trânsito",
content: "Parecer sobre responsabilidade civil em acidentes...",
createdAt: new Date("2024-03-10"),
category: "Direito Civil",
},
{
id: "base-4",
title: "Dissolução de Sociedade - Procedimentos e Requisitos",
content: "Análise completa sobre dissolução societária...",
createdAt: new Date("2024-01-25"),
category: "Direito Empresarial",
},
{
id: "base-5",
title: "Direitos do Consumidor - Vícios em Produtos",
content: "Parecer sobre direitos do consumidor e garantias...",
createdAt: new Date("2024-02-05"),
category: "Direito do Consumidor",
},
];
export const AgentSearch = ({ onSelectOpinion }: AgentSearchProps) => {
const [searchTerm, setSearchTerm] = useState("");
const [searchResults, setSearchResults] = useState<LegalOpinion[]>([]);
const [searchResults, setSearchResults] = useState<OpinionRecord[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(5);
const [itemsPerPage, setItemsPerPage] = useState(10);
const [isLoading, setIsLoading] = useState(false);
const { toast } = useToast();
const handleSearch = () => {
// Busca pareceres da API
const handleSearch = async () => {
setIsLoading(true);
try {
const data = await agentService.getOpinions({
page: currentPage,
per_page: itemsPerPage,
search: searchTerm,
});
setSearchResults(data);
} catch (error: any) {
console.error('Erro ao buscar pareceres:', error);
toast({
title: "Erro ao buscar pareceres",
description: error.message || "Não foi possível buscar os pareceres.",
variant: "destructive",
});
setSearchResults([]);
} finally {
setIsLoading(false);
}
};
// Busca automaticamente quando a página ou itemsPerPage mudam
useEffect(() => {
if (searchTerm.trim()) {
handleSearch();
}
}, [currentPage, itemsPerPage]);
// Debounce para busca automática
useEffect(() => {
if (!searchTerm.trim()) {
setSearchResults([]);
return;
}
const results = mockOpinionsDatabase.filter(
(opinion) =>
opinion.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
opinion.content.toLowerCase().includes(searchTerm.toLowerCase()) ||
opinion.category?.toLowerCase().includes(searchTerm.toLowerCase())
);
const timer = setTimeout(() => {
if (currentPage === 1) {
handleSearch();
} else {
setCurrentPage(1); // Volta para primeira página ao buscar
}
}, 500);
setSearchResults(results);
setCurrentPage(1);
};
return () => clearTimeout(timer);
}, [searchTerm]);
const totalPages = Math.ceil(searchResults.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const paginatedResults = searchResults.slice(startIndex, startIndex + itemsPerPage);
return (
<div className="flex flex-col h-full bg-background">
@@ -92,24 +85,30 @@ export const AgentSearch = ({ onSelectOpinion }: AgentSearchProps) => {
<Input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Digite título, frase ou categoria..."
placeholder="Digite título ou categoria..."
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
/>
<Button onClick={handleSearch} className="gap-2">
<Button onClick={handleSearch} className="gap-2" disabled={isLoading}>
<Search className="w-4 h-4" />
Buscar
{isLoading ? 'Buscando...' : 'Buscar'}
</Button>
</div>
<p className="text-sm text-muted-foreground">
Base com {mockOpinionsDatabase.length} pareceres disponíveis
</p>
{searchResults.length > 0 && (
<p className="text-sm text-muted-foreground">
{searchResults.length} {searchResults.length === 1 ? 'parecer encontrado' : 'pareceres encontrados'}
</p>
)}
</div>
<div className="flex-1 flex flex-col">
<ScrollArea className="flex-1 p-6">
{searchResults.length > 0 ? (
{isLoading ? (
<div className="text-center text-muted-foreground py-12">
<p>Buscando pareceres...</p>
</div>
) : searchResults.length > 0 ? (
<div className="space-y-3">
{paginatedResults.map((opinion) => (
{searchResults.map((opinion) => (
<div
key={opinion.id}
className="p-4 border border-border rounded-lg hover:bg-accent/50 cursor-pointer transition-colors"
@@ -118,13 +117,13 @@ export const AgentSearch = ({ onSelectOpinion }: AgentSearchProps) => {
<div className="flex items-start gap-3">
<FileText className="w-5 h-5 text-primary mt-1" />
<div className="flex-1">
<h3 className="font-semibold text-foreground">{opinion.title}</h3>
<h3 className="font-semibold text-foreground">{opinion.titulo}</h3>
<div className="flex gap-4 mt-2 text-sm text-muted-foreground">
<span>{opinion.category}</span>
<span>{new Date(opinion.createdAt).toLocaleDateString('pt-BR')}</span>
<span>{opinion.categoria || '-'}</span>
<span>{new Date(opinion.created_at).toLocaleDateString('pt-BR')}</span>
</div>
<p className="text-sm text-muted-foreground mt-2 line-clamp-2">
{opinion.content}
{opinion.instrucoes}
</p>
</div>
</div>
+203 -50
View File
@@ -1,5 +1,5 @@
import { useState } from "react";
import { Plus, Search, Eye, Trash2, ArrowUpDown } from "lucide-react";
import { useState, useEffect } from "react";
import { Plus, Search, Eye, Trash2, ArrowUpDown, Download } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
@@ -17,30 +17,84 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { OpinionDialog } from "./OpinionDialog";
import { AgentSearch } from "./AgentSearch";
import { agentService, OpinionRecord } from "@/services/agent";
import { useToast } from "@/hooks/use-toast";
export interface LegalOpinion {
id: string;
title: string;
content: string;
createdAt: Date;
category?: string;
}
type SortField = "title" | "createdAt" | "category";
type SortField = "titulo" | "created_at" | "categoria";
type SortOrder = "asc" | "desc";
export const AgentView = () => {
const [opinions, setOpinions] = useState<LegalOpinion[]>([]);
const [opinions, setOpinions] = useState<OpinionRecord[]>([]);
const [searchTerm, setSearchTerm] = useState("");
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(10);
const [sortField, setSortField] = useState<SortField>("createdAt");
const [sortField, setSortField] = useState<SortField>("created_at");
const [sortOrder, setSortOrder] = useState<SortOrder>("desc");
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [selectedOpinion, setSelectedOpinion] = useState<LegalOpinion | null>(null);
const [selectedOpinion, setSelectedOpinion] = useState<OpinionRecord | null>(null);
const [showSearch, setShowSearch] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [opinionToDelete, setOpinionToDelete] = useState<OpinionRecord | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
const { toast } = useToast();
// Carrega pareceres da API
const loadOpinions = async () => {
setIsLoading(true);
try {
const data = await agentService.getOpinions({
page: currentPage,
per_page: itemsPerPage,
search: searchTerm,
});
setOpinions(data);
} catch (error: any) {
console.error('Erro ao carregar pareceres:', error);
toast({
title: "Erro ao carregar pareceres",
description: error.message || "Não foi possível carregar a lista de pareceres.",
variant: "destructive",
});
} finally {
setIsLoading(false);
}
};
// Carrega pareceres ao montar o componente e quando mudar página/busca
useEffect(() => {
loadOpinions();
}, [currentPage, itemsPerPage]);
// Debounce para busca
useEffect(() => {
const timer = setTimeout(() => {
if (currentPage === 1) {
loadOpinions();
} else {
setCurrentPage(1); // Volta para a primeira página ao buscar
}
}, 500);
return () => clearTimeout(timer);
}, [searchTerm]);
const handleSort = (field: SortField) => {
if (sortField === field) {
@@ -51,39 +105,26 @@ export const AgentView = () => {
}
};
const filteredOpinions = opinions.filter(
(op) =>
op.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
op.category?.toLowerCase().includes(searchTerm.toLowerCase())
);
const sortedOpinions = [...filteredOpinions].sort((a, b) => {
const sortedOpinions = [...opinions].sort((a, b) => {
const multiplier = sortOrder === "asc" ? 1 : -1;
if (sortField === "createdAt") {
return multiplier * (new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
if (sortField === "created_at") {
return multiplier * (new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
}
const aValue = a[sortField] || "";
const bValue = b[sortField] || "";
return multiplier * aValue.toString().localeCompare(bValue.toString());
});
const totalPages = Math.ceil(sortedOpinions.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const paginatedOpinions = sortedOpinions.slice(startIndex, startIndex + itemsPerPage);
const handleOpinionCreated = (newOpinion: LegalOpinion) => {
setOpinions([newOpinion, ...opinions]);
setIsDialogOpen(false);
setSelectedOpinion(null);
const handleOpinionCreated = () => {
// Recarrega a lista após criar um novo parecer
loadOpinions();
};
const handleDeleteOpinion = (id: string) => {
setOpinions(opinions.filter(op => op.id !== id));
};
const handleViewOpinion = (opinion: LegalOpinion) => {
const handleViewOpinion = (opinion: OpinionRecord) => {
setSelectedOpinion(opinion);
setIsDialogOpen(true);
};
@@ -93,12 +134,69 @@ export const AgentView = () => {
setIsDialogOpen(true);
};
const handleSelectFromSearch = (opinion: LegalOpinion) => {
const handleSelectFromSearch = (opinion: OpinionRecord) => {
setSelectedOpinion(opinion);
setShowSearch(false);
setIsDialogOpen(true);
};
const handleDownloadVersion = async (opinion: OpinionRecord, version: 'v1' | 'v2') => {
const fileUrl = version === 'v1' ? opinion.file_url : opinion.file_url_melhoria;
if (!fileUrl) {
toast({
title: "Arquivo não disponível",
description: `A ${version === 'v1' ? 'versão 1' : 'versão melhorada'} ainda não está disponível.`,
variant: "destructive",
});
return;
}
try {
const fileName = `${opinion.titulo}_${version === 'v1' ? 'v1' : 'melhorada'}.docx`;
await agentService.downloadOpinion(fileUrl, fileName);
toast({
title: "Download iniciado",
description: "O parecer está sendo baixado.",
});
} catch (error: any) {
toast({
title: "Erro ao fazer download",
description: error.message || "Não foi possível baixar o arquivo.",
variant: "destructive",
});
}
};
const handleDeleteOpinion = async () => {
if (!opinionToDelete) return;
setIsDeleting(true);
try {
await agentService.deleteOpinion(opinionToDelete.id);
toast({
title: "Parecer excluído",
description: "O parecer foi excluído com sucesso.",
});
// Recarrega a lista de pareceres
await loadOpinions();
// Fecha o diálogo de confirmação
setOpinionToDelete(null);
} catch (error: any) {
toast({
title: "Erro ao excluir parecer",
description: error.message || "Não foi possível excluir o parecer.",
variant: "destructive",
});
} finally {
setIsDeleting(false);
}
};
if (showSearch) {
return (
<div className="flex flex-col h-full">
@@ -116,7 +214,7 @@ export const AgentView = () => {
<div className="flex flex-col h-full bg-background pb-16 md:pb-0">
<div className="p-3 md:p-6 border-b border-border">
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-3 mb-4">
<h1 className="text-xl md:text-3xl font-bold text-foreground">Agente de Parecer Jurídico</h1>
<h1 className="text-xl md:text-3xl font-bold text-foreground">Gerador de Modelos de Parecer Jurídico</h1>
<div className="flex gap-2 w-full md:w-auto">
<Button onClick={() => setShowSearch(true)} variant="outline" className="gap-1 md:gap-2 flex-1 md:flex-none text-xs md:text-sm">
<Search className="w-3 h-3 md:w-4 md:h-4" />
@@ -169,7 +267,7 @@ export const AgentView = () => {
<TableHead className="min-w-[200px]">
<Button
variant="ghost"
onClick={() => handleSort("title")}
onClick={() => handleSort("titulo")}
className="flex items-center gap-1 font-semibold text-xs md:text-sm p-1 md:p-2"
>
Título
@@ -179,7 +277,7 @@ export const AgentView = () => {
<TableHead className="hidden md:table-cell">
<Button
variant="ghost"
onClick={() => handleSort("category")}
onClick={() => handleSort("categoria")}
className="flex items-center gap-1 font-semibold text-sm"
>
Categoria
@@ -189,7 +287,7 @@ export const AgentView = () => {
<TableHead className="hidden sm:table-cell">
<Button
variant="ghost"
onClick={() => handleSort("createdAt")}
onClick={() => handleSort("created_at")}
className="flex items-center gap-1 font-semibold text-xs md:text-sm p-1 md:p-2"
>
Data
@@ -200,7 +298,13 @@ export const AgentView = () => {
</TableRow>
</TableHeader>
<TableBody>
{paginatedOpinions.length === 0 ? (
{isLoading ? (
<TableRow>
<TableCell colSpan={4} 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">
{searchTerm
@@ -209,12 +313,12 @@ export const AgentView = () => {
</TableCell>
</TableRow>
) : (
paginatedOpinions.map((opinion) => (
sortedOpinions.map((opinion) => (
<TableRow key={opinion.id}>
<TableCell className="font-medium text-xs md:text-sm">{opinion.title}</TableCell>
<TableCell className="hidden md:table-cell text-sm">{opinion.category || "-"}</TableCell>
<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 sm:table-cell text-xs md:text-sm">
{new Date(opinion.createdAt).toLocaleDateString("pt-BR")}
{new Date(opinion.created_at).toLocaleDateString("pt-BR")}
</TableCell>
<TableCell>
<div className="flex items-center justify-center gap-1">
@@ -227,12 +331,40 @@ export const AgentView = () => {
>
<Eye className="w-3 h-3 md:w-4 md:h-4" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
title="Baixar"
className="h-7 w-7 md:h-9 md:w-9"
>
<Download className="w-3 h-3 md:w-4 md:h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => handleDownloadVersion(opinion, 'v1')}
disabled={!opinion.file_url}
>
<Download className="w-4 h-4 mr-2" />
Versão 1
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleDownloadVersion(opinion, 'v2')}
disabled={!opinion.file_url_melhoria}
>
<Download className="w-4 h-4 mr-2" />
Versão Melhorada
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button
variant="ghost"
size="icon"
onClick={() => handleDeleteOpinion(opinion.id)}
className="text-destructive hover:text-destructive h-7 w-7 md:h-9 md:w-9"
onClick={() => setOpinionToDelete(opinion)}
title="Excluir"
className="h-7 w-7 md:h-9 md:w-9 text-destructive hover:text-destructive"
>
<Trash2 className="w-3 h-3 md:w-4 md:h-4" />
</Button>
@@ -245,10 +377,10 @@ export const AgentView = () => {
</Table>
</div>
{totalPages > 1 && (
{sortedOpinions.length > 0 && (
<div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-2 mt-4">
<p className="text-xs md:text-sm text-muted-foreground text-center sm:text-left">
{startIndex + 1}-{Math.min(startIndex + itemsPerPage, sortedOpinions.length)} de {sortedOpinions.length}
Mostrando {sortedOpinions.length} {sortedOpinions.length === 1 ? 'parecer' : 'pareceres'}
</p>
<div className="flex gap-1 md:gap-2 justify-center">
<Button
@@ -307,6 +439,27 @@ export const AgentView = () => {
selectedOpinion={selectedOpinion}
onOpinionCreated={handleOpinionCreated}
/>
<AlertDialog open={!!opinionToDelete} onOpenChange={(open) => !open && setOpinionToDelete(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Confirmar exclusão</AlertDialogTitle>
<AlertDialogDescription>
Tem certeza que deseja excluir o parecer "{opinionToDelete?.titulo}"? Esta ação não pode ser desfeita.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isDeleting}>Cancelar</AlertDialogCancel>
<AlertDialogAction
onClick={handleDeleteOpinion}
disabled={isDeleting}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{isDeleting ? "Excluindo..." : "Excluir"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
};
+113 -99
View File
@@ -1,5 +1,4 @@
import { useState, useEffect } from "react";
import axios from "axios";
import { Download, Sparkles } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
@@ -14,13 +13,13 @@ 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 { LegalOpinion } from "./AgentView";
import { agentService, OpinionRecord } from "@/services/agent";
interface OpinionDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
selectedOpinion: LegalOpinion | null;
onOpinionCreated: (opinion: LegalOpinion) => void;
selectedOpinion: OpinionRecord | null;
onOpinionCreated: () => void;
}
export const OpinionDialog = ({
@@ -32,23 +31,23 @@ export const OpinionDialog = ({
const [title, setTitle] = useState("");
const [category, setCategory] = useState("");
const [instructions, setInstructions] = useState("");
const [generatedContent, setGeneratedContent] = useState("");
const [isGenerating, setIsGenerating] = useState(false);
const [createdOpinion, setCreatedOpinion] = useState<OpinionRecord | null>(null);
const { toast } = useToast();
useEffect(() => {
if (selectedOpinion) {
setTitle(selectedOpinion.title);
setCategory(selectedOpinion.category || "");
setGeneratedContent(selectedOpinion.content);
setInstructions("");
setTitle(selectedOpinion.titulo);
setCategory(selectedOpinion.categoria || "");
setInstructions(selectedOpinion.instrucoes || "");
setCreatedOpinion(selectedOpinion);
} else {
setTitle("");
setCategory("");
setInstructions("");
setGeneratedContent("");
setCreatedOpinion(null);
}
}, [selectedOpinion]);
}, [selectedOpinion, open]);
const handleGenerate = async () => {
if (!instructions.trim()) {
@@ -72,53 +71,39 @@ export const OpinionDialog = ({
setIsGenerating(true);
try {
const response = await axios.post(
"https://prod-hgtx-intelligence-n8n.hgtx.com.br/webhook/codex/gepam/parecer-tecnico",
{
titulo: title,
categoria: category,
instrucoes: instructions,
},
{
headers: {
"Content-Type": "application/json",
},
}
);
const content = response.data.parecer || response.data.content || JSON.stringify(response.data, null, 2);
setGeneratedContent(content);
setIsGenerating(false);
const newOpinion: LegalOpinion = {
id: selectedOpinion?.id || Date.now().toString(),
title: title,
content: content,
createdAt: new Date(),
category: category || undefined,
};
onOpinionCreated(newOpinion);
toast({
title: "Parecer gerado com sucesso!",
description: "O parecer está pronto para download.",
const response = await agentService.createOpinion({
titulo: title,
categoria: category,
instrucoes: instructions,
});
} catch (error) {
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
onOpinionCreated();
// Fecha o dialog após sucesso
setIsGenerating(false);
onOpenChange(false);
// Limpa os campos
setTitle("");
setCategory("");
setInstructions("");
setCreatedOpinion(null);
} else {
throw new Error('Falha ao criar parecer');
}
} catch (error: any) {
console.error("Erro ao gerar parecer:", error);
setIsGenerating(false);
let errorMessage = "Não foi possível gerar o parecer. Tente novamente.";
if (axios.isAxiosError(error)) {
if (error.response) {
errorMessage = error.response.data?.message || `Erro do servidor: ${error.response.status}`;
} else if (error.request) {
errorMessage = "Sem resposta do servidor. Verifique sua conexão.";
}
}
const errorMessage = error.message || "Não foi possível gerar o parecer. Tente novamente.";
toast({
title: "Erro ao gerar parecer",
description: errorMessage,
@@ -127,22 +112,45 @@ export const OpinionDialog = ({
}
};
const handleDownloadDocx = () => {
const content = generatedContent;
const blob = new Blob([content], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${title || "parecer"}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
const handleDownloadVersion = async (version: 'v1' | 'v2') => {
if (!createdOpinion && !selectedOpinion) {
toast({
title: "Nenhum parecer disponível",
description: "Por favor, gere um parecer primeiro.",
variant: "destructive",
});
return;
}
toast({
title: "Download iniciado",
description: "O parecer está sendo baixado.",
});
const opinion = createdOpinion || selectedOpinion;
if (!opinion) return;
const fileUrl = version === 'v1' ? opinion.file_url : opinion.file_url_melhoria;
if (!fileUrl) {
toast({
title: "Arquivo não disponível",
description: `A ${version === 'v1' ? 'versão 1' : 'versão melhorada'} ainda não está disponível.`,
variant: "destructive",
});
return;
}
try {
const fileName = `${opinion.titulo}_${version === 'v1' ? 'v1' : 'melhorada'}.docx`;
await agentService.downloadOpinion(fileUrl, fileName);
toast({
title: "Download iniciado",
description: "O parecer está sendo baixado.",
});
} catch (error: any) {
toast({
title: "Erro ao fazer download",
description: error.message || "Não foi possível baixar o arquivo.",
variant: "destructive",
});
}
};
return (
@@ -150,11 +158,11 @@ export const OpinionDialog = ({
<DialogContent className="max-w-[95vw] md:max-w-4xl max-h-[90vh]">
<DialogHeader>
<DialogTitle>
{selectedOpinion ? "Gerar Novo Modelo do Parecer" : "Novo Parecer Jurídico"}
{selectedOpinion ? "Visualizar Parecer" : "Novo Parecer Jurídico"}
</DialogTitle>
<DialogDescription>
{selectedOpinion
? "Forneça instruções para gerar um novo modelo baseado neste parecer"
? "Visualize os detalhes do parecer e faça o download das versões disponíveis"
: "Preencha os dados e instruções para gerar um novo parecer com IA"}
</DialogDescription>
</DialogHeader>
@@ -196,40 +204,46 @@ export const OpinionDialog = ({
/>
</div>
<Button
onClick={handleGenerate}
disabled={isGenerating}
className="w-full gap-2"
>
{isGenerating ? (
<>Gerando parecer...</>
) : (
<>
<Sparkles className="w-4 h-4" />
Gerar Parecer com IA
</>
)}
</Button>
{!selectedOpinion && (
<Button
onClick={handleGenerate}
disabled={isGenerating}
className="w-full gap-2"
>
{isGenerating ? (
<>Gerando parecer...</>
) : (
<>
<Sparkles className="w-4 h-4" />
Gerar Parecer com IA
</>
)}
</Button>
)}
{generatedContent && (
<div className="space-y-2 pt-4 border-t">
<div className="flex items-center justify-between">
<Label>Parecer Gerado</Label>
{selectedOpinion && (
<div className="space-y-3 pt-4 border-t">
<Label>Downloads Disponíveis</Label>
<div className="flex flex-col gap-2">
<Button
onClick={handleDownloadDocx}
onClick={() => handleDownloadVersion('v1')}
variant="outline"
size="sm"
className="gap-2"
className="w-full gap-2 justify-start"
disabled={!selectedOpinion.file_url}
>
<Download className="w-4 h-4" />
Baixar DOCX
Baixar Versão 1
</Button>
<Button
onClick={() => handleDownloadVersion('v2')}
variant="outline"
className="w-full gap-2 justify-start"
disabled={!selectedOpinion.file_url_melhoria}
>
<Download className="w-4 h-4" />
Baixar Versão Melhorada
</Button>
</div>
<ScrollArea className="h-[300px] rounded-md border p-4">
<pre className="whitespace-pre-wrap font-sans text-sm">
{generatedContent}
</pre>
</ScrollArea>
</div>
)}
</div>