"use client"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { format } from "date-fns"; import { ArchiveIcon, Loader2, MessageCircleMore, MoreHorizontal, RotateCcwIcon, Search, Trash2, X, } from "lucide-react"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useCallback, useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from "@/components/ui/sheet"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useDebouncedValue } from "@/hooks/use-debounced-value"; import { deleteThread, fetchThreads, searchThreads, type ThreadListItem, updateThread, } from "@/lib/chat/thread-persistence"; import { cn } from "@/lib/utils"; interface AllChatsSidebarProps { open: boolean; onOpenChange: (open: boolean) => void; searchSpaceId: string; } export function AllChatsSidebar({ open, onOpenChange, searchSpaceId }: AllChatsSidebarProps) { const t = useTranslations("sidebar"); const router = useRouter(); const queryClient = useQueryClient(); const [deletingThreadId, setDeletingThreadId] = useState(null); const [archivingThreadId, setArchivingThreadId] = useState(null); const [searchQuery, setSearchQuery] = useState(""); const [showArchived, setShowArchived] = useState(false); const debouncedSearchQuery = useDebouncedValue(searchQuery, 300); const isSearchMode = !!debouncedSearchQuery.trim(); // Fetch all threads (when not searching) const { data: threadsData, error: threadsError, isLoading: isLoadingThreads, } = useQuery({ queryKey: ["all-threads", searchSpaceId], queryFn: () => fetchThreads(Number(searchSpaceId)), enabled: !!searchSpaceId && open && !isSearchMode, }); // Search threads (when searching) const { data: searchData, error: searchError, isLoading: isLoadingSearch, } = useQuery({ queryKey: ["search-threads", searchSpaceId, debouncedSearchQuery], queryFn: () => searchThreads(Number(searchSpaceId), debouncedSearchQuery.trim()), enabled: !!searchSpaceId && open && isSearchMode, }); // Handle thread navigation const handleThreadClick = useCallback( (threadId: number) => { router.push(`/dashboard/${searchSpaceId}/new-chat/${threadId}`); onOpenChange(false); }, [router, onOpenChange, searchSpaceId] ); // Handle thread deletion const handleDeleteThread = useCallback( async (threadId: number) => { setDeletingThreadId(threadId); try { await deleteThread(threadId); toast.success(t("chat_deleted") || "Chat deleted successfully"); // Invalidate queries to refresh the list queryClient.invalidateQueries({ queryKey: ["all-threads", searchSpaceId] }); queryClient.invalidateQueries({ queryKey: ["search-threads", searchSpaceId] }); queryClient.invalidateQueries({ queryKey: ["threads", searchSpaceId] }); } catch (error) { console.error("Error deleting thread:", error); toast.error(t("error_deleting_chat") || "Failed to delete chat"); } finally { setDeletingThreadId(null); } }, [queryClient, searchSpaceId, t] ); // Handle thread archive/unarchive const handleToggleArchive = useCallback( async (threadId: number, currentlyArchived: boolean) => { setArchivingThreadId(threadId); try { await updateThread(threadId, { archived: !currentlyArchived }); toast.success( currentlyArchived ? t("chat_unarchived") || "Chat restored" : t("chat_archived") || "Chat archived" ); queryClient.invalidateQueries({ queryKey: ["all-threads", searchSpaceId] }); queryClient.invalidateQueries({ queryKey: ["search-threads", searchSpaceId] }); queryClient.invalidateQueries({ queryKey: ["threads", searchSpaceId] }); } catch (error) { console.error("Error archiving thread:", error); toast.error(t("error_archiving_chat") || "Failed to archive chat"); } finally { setArchivingThreadId(null); } }, [queryClient, searchSpaceId, t] ); // Clear search const handleClearSearch = useCallback(() => { setSearchQuery(""); }, []); // Determine which data source to use let threads: ThreadListItem[] = []; if (isSearchMode) { threads = searchData ?? []; } else if (threadsData) { threads = showArchived ? threadsData.archived_threads : threadsData.threads; } const isLoading = isSearchMode ? isLoadingSearch : isLoadingThreads; const error = isSearchMode ? searchError : threadsError; // Get counts for tabs const activeCount = threadsData?.threads.length ?? 0; const archivedCount = threadsData?.archived_threads.length ?? 0; return ( {t("all_chats") || "All Chats"} {t("all_chats_description") || "Browse and manage all your chats"} {/* Search Input */}
setSearchQuery(e.target.value)} className="pl-9 pr-8 h-9 border-0 focus-visible:ring-0 focus-visible:border-0 shadow-none" /> {searchQuery && ( )}
{/* Tab toggle for active/archived (only show when not searching) */} {!isSearchMode && (
)}
{isLoading ? (
) : error ? (
{t("error_loading_chats") || "Error loading chats"}
) : threads.length > 0 ? (
{threads.map((thread) => { const isDeleting = deletingThreadId === thread.id; const isArchiving = archivingThreadId === thread.id; const isBusy = isDeleting || isArchiving; return (
{/* Main clickable area for navigation */}

{t("updated") || "Updated"}:{" "} {format(new Date(thread.updatedAt), "MMM d, yyyy 'at' h:mm a")}

{/* Actions dropdown */} handleToggleArchive(thread.id, thread.archived)} disabled={isArchiving} > {thread.archived ? ( <> {t("unarchive") || "Restore"} ) : ( <> {t("archive") || "Archive"} )} handleDeleteThread(thread.id)} className="text-destructive focus:text-destructive" > {t("delete") || "Delete"}
); })}
) : isSearchMode ? (

{t("no_chats_found") || "No chats found"}

{t("try_different_search") || "Try a different search term"}

) : (

{showArchived ? t("no_archived_chats") || "No archived chats" : t("no_chats") || "No chats yet"}

{!showArchived && (

{t("start_new_chat_hint") || "Start a new chat from the chat page"}

)}
)}
); }