import { useState, useEffect } from "react"; import { Search, Folder, FolderPlus, MessageSquare, MoreVertical, Trash2, Edit, FolderInput, ChevronLeft, ChevronRight, Plus } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useToast } from "@/hooks/use-toast"; import { chatService, ChatRecord, FolderRecord } from "@/services/chat"; import { formatDistanceToNow } from "date-fns"; import { ptBR } from "date-fns/locale"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; interface ChatSidebarProps { isCollapsed: boolean; onToggleCollapse: () => void; onNewChat: () => void; onSelectChat?: (chat: ChatRecord) => void; currentChatId?: string; } export const ChatSidebar = ({ isCollapsed, onToggleCollapse, onNewChat, onSelectChat, currentChatId }: ChatSidebarProps) => { const { toast } = useToast(); const [searchQuery, setSearchQuery] = useState(""); const [isCreateFolderOpen, setIsCreateFolderOpen] = useState(false); const [isDeleteFolderOpen, setIsDeleteFolderOpen] = useState(false); const [isDeleteChatOpen, setIsDeleteChatOpen] = useState(false); const [isRenameFolderOpen, setIsRenameFolderOpen] = useState(false); const [newFolderName, setNewFolderName] = useState(""); const [renamingFolder, setRenamingFolder] = useState(null); const [renamedFolderName, setRenamedFolderName] = useState(""); const [deletingFolder, setDeletingFolder] = useState(null); const [deletingChat, setDeletingChat] = useState(null); const [isLoading, setIsLoading] = useState(false); // Estado carregado do banco de dados via chatService const [chats, setChats] = useState([]); const [folders, setFolders] = useState([]); const [expandedFolders, setExpandedFolders] = useState>(new Set()); // Carrega chats e pastas do banco de dados quando o componente monta useEffect(() => { loadChatsAndFolders(); // Escuta evento customizado para recarregar quando chat é atualizado const handleChatUpdate = () => { loadChatsAndFolders(); }; window.addEventListener('chatUpdated', handleChatUpdate); // Cleanup return () => { window.removeEventListener('chatUpdated', handleChatUpdate); }; }, []); const loadChatsAndFolders = async () => { setIsLoading(true); try { const data = await chatService.getChatsAndFolders(); console.log('Dados carregados:', data); setChats(data.chats); setFolders(data.folders); // Expande todas as pastas por padrão setExpandedFolders(new Set(data.folders.map(f => f.id))); } catch (error: any) { console.error('Erro ao carregar chats e pastas:', error); toast({ title: "Erro ao carregar dados", description: error.message || "Não foi possível carregar chats e pastas.", variant: "destructive", }); // Define arrays vazios em caso de erro setChats([]); setFolders([]); } finally { setIsLoading(false); } }; const handleCreateFolder = async () => { if (newFolderName.trim()) { try { await chatService.createFolder(newFolderName); await loadChatsAndFolders(); setNewFolderName(""); setIsCreateFolderOpen(false); toast({ title: "Pasta criada", description: `Pasta "${newFolderName}" criada com sucesso.`, }); } catch (error: any) { console.error('Erro ao criar pasta:', error); toast({ title: "Erro ao criar pasta", description: error.message || "Não foi possível criar a pasta. Tente novamente.", variant: "destructive", }); } } }; const handleDeleteFolder = async () => { if (deletingFolder) { try { await chatService.deleteFolder(deletingFolder.id); await loadChatsAndFolders(); setDeletingFolder(null); setIsDeleteFolderOpen(false); toast({ title: "Pasta excluída", description: "A pasta foi excluída com sucesso.", }); } catch (error: any) { console.error('Erro ao excluir pasta:', error); toast({ title: "Erro ao excluir pasta", description: error.message || "Não foi possível excluir a pasta. Tente novamente.", variant: "destructive", }); } } }; const openDeleteFolder = (folder: FolderRecord) => { setDeletingFolder(folder); setIsDeleteFolderOpen(true); }; const openRenameFolder = (folder: FolderRecord) => { setRenamingFolder(folder); setRenamedFolderName(folder.name); setIsRenameFolderOpen(true); }; const handleRenameFolder = async () => { if (renamingFolder && renamedFolderName.trim()) { try { await chatService.renameFolder(renamingFolder.id, renamedFolderName); await loadChatsAndFolders(); setRenamingFolder(null); setRenamedFolderName(""); setIsRenameFolderOpen(false); toast({ title: "Pasta renomeada", description: `Pasta renomeada para "${renamedFolderName}" com sucesso.`, }); } catch (error: any) { console.error('Erro ao renomear pasta:', error); toast({ title: "Erro ao renomear pasta", description: error.message || "Não foi possível renomear a pasta. Tente novamente.", variant: "destructive", }); } } }; const toggleFolder = (folderId: string) => { const newExpanded = new Set(expandedFolders); if (newExpanded.has(folderId)) { newExpanded.delete(folderId); } else { newExpanded.add(folderId); } setExpandedFolders(newExpanded); }; const moveToFolder = async (chatId: string, folderId: string) => { try { await chatService.moveChatToFolder(chatId, folderId); await loadChatsAndFolders(); const folder = folders.find(f => f.id === folderId); toast({ title: "Chat movido", description: `Movido para a pasta "${folder?.name}".`, }); } catch (error: any) { console.error('Erro ao mover chat:', error); toast({ title: "Erro ao mover chat", description: error.message || "Não foi possível mover o chat. Tente novamente.", variant: "destructive", }); } }; const handleDeleteChat = async () => { if (deletingChat) { try { await chatService.deleteChat(deletingChat.id); await loadChatsAndFolders(); setDeletingChat(null); setIsDeleteChatOpen(false); toast({ title: "Chat excluído", description: "A conversa foi excluída com sucesso.", }); } catch (error: any) { console.error('Erro ao excluir chat:', error); toast({ title: "Erro ao excluir chat", description: error.message || "Não foi possível excluir o chat. Tente novamente.", variant: "destructive", }); } } }; const openDeleteChat = (chat: ChatRecord) => { setDeletingChat(chat); setIsDeleteChatOpen(true); }; const handleSelectChat = (chat: ChatRecord) => { if (onSelectChat) { onSelectChat(chat); } }; // Filtrar chats pela busca const filteredChats = chats.filter((chat) => { const searchLower = searchQuery.toLowerCase(); const titleMatch = chat.title.toLowerCase().includes(searchLower); return titleMatch; }); // Separar chats sem pasta (folder_id é null) const chatsWithoutFolder = filteredChats.filter((c) => c.folder_id === null); // Agrupar chats por pasta (quando folder_id === folder.id) const chatsByFolder = folders.reduce((acc, folder) => { acc[folder.id] = filteredChats.filter((c) => c.folder_id === folder.id); return acc; }, {} as Record); if (isCollapsed) { return (
); } return (
{/* Header with collapse button */}

Conversas

{/* Search */}
setSearchQuery(e.target.value)} placeholder="Pesquisar conversas..." className="pl-9 bg-muted/50" />
Criar Nova Pasta Organize suas conversas em pastas personalizadas.
setNewFolderName(e.target.value)} placeholder="Ex: Projetos, Estudos..." onKeyDown={(e) => e.key === "Enter" && handleCreateFolder()} />
{/* Rename Folder Dialog */} Renomear Pasta Digite o novo nome para a pasta "{renamingFolder?.name}".
setRenamedFolderName(e.target.value)} placeholder="Ex: Projetos, Estudos..." onKeyDown={(e) => e.key === "Enter" && handleRenameFolder()} autoFocus />
{/* Delete Folder Alert */} Excluir Pasta? Esta ação não pode ser desfeita. A pasta "{deletingFolder?.name}" será excluída. Cancelar Excluir
{/* Conversations List */}
{isLoading ? (
) : ( <> {/* Folders */} {folders.map((folder) => (
openRenameFolder(folder)} > Renomear Pasta openDeleteFolder(folder)} > Excluir Pasta
{expandedFolders.has(folder.id) && chatsByFolder[folder.id]?.length > 0 && (
{chatsByFolder[folder.id].map((chat) => ( handleSelectChat(chat)} folders={folders} onMoveToFolder={moveToFolder} onDelete={() => openDeleteChat(chat)} /> ))}
)}
))} {/* Chats without folder */} {chatsWithoutFolder.length > 0 && (
Sem Pasta
{chatsWithoutFolder.map((chat) => ( handleSelectChat(chat)} folders={folders} onMoveToFolder={moveToFolder} onDelete={() => openDeleteChat(chat)} /> ))}
)} {/* Empty state */} {!isLoading && chats.length === 0 && (

Nenhuma conversa ainda

Clique em "Novo Chat" para começar

)} )}
{/* Delete Chat Alert */} Excluir Conversa? Esta ação não pode ser desfeita. A conversa "{deletingChat?.title}" será permanentemente excluída. Cancelar Excluir
); }; interface ChatItemProps { chat: ChatRecord; isSelected: boolean; onSelect: () => void; folders: FolderRecord[]; onMoveToFolder: (chatId: string, folderId: string) => void; onDelete: () => void; } const ChatItem = ({ chat, isSelected, onSelect, folders, onMoveToFolder, onDelete, }: ChatItemProps) => { // Formata timestamp relativo const timeAgo = formatDistanceToNow(new Date(chat.updated_at), { addSuffix: true, locale: ptBR, }); return (

{chat.title}

{timeAgo}

e.stopPropagation()}> {/* Opções para mover para pastas */} {folders.filter(f => f.id !== chat.folder_id).map((folder) => ( onMoveToFolder(chat.id, folder.id)} > Mover para {folder.name} ))} Excluir
); };