"use client"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { format } from "date-fns"; import { FileText, Loader2, MoreHorizontal, Plus, Search, Trash2, X } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; import { useParams, useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useState } from "react"; import { createPortal } from "react-dom"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useDebouncedValue } from "@/hooks/use-debounced-value"; import { documentsApiService } from "@/lib/apis/documents-api.service"; import { notesApiService } from "@/lib/apis/notes-api.service"; import { cn } from "@/lib/utils"; interface AllNotesSidebarProps { open: boolean; onOpenChange: (open: boolean) => void; searchSpaceId: string; onAddNote?: () => void; onCloseMobileSidebar?: () => void; } export function AllNotesSidebar({ open, onOpenChange, searchSpaceId, onAddNote, onCloseMobileSidebar, }: AllNotesSidebarProps) { const t = useTranslations("sidebar"); const router = useRouter(); const params = useParams(); const queryClient = useQueryClient(); // Get the current note ID from URL to highlight the open note const currentNoteId = params.note_id ? Number(params.note_id) : null; const [deletingNoteId, setDeletingNoteId] = useState(null); const [searchQuery, setSearchQuery] = useState(""); const [mounted, setMounted] = useState(false); const [openDropdownId, setOpenDropdownId] = useState(null); const debouncedSearchQuery = useDebouncedValue(searchQuery, 300); // Handle mounting for portal useEffect(() => { setMounted(true); }, []); // Handle escape key useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === "Escape" && open) { onOpenChange(false); } }; document.addEventListener("keydown", handleEscape); return () => document.removeEventListener("keydown", handleEscape); }, [open, onOpenChange]); // Lock body scroll when open useEffect(() => { if (open) { document.body.style.overflow = "hidden"; } else { document.body.style.overflow = ""; } return () => { document.body.style.overflow = ""; }; }, [open]); // Fetch all notes (when no search query) const { data: notesData, error: notesError, isLoading: isLoadingNotes, } = useQuery({ queryKey: ["all-notes", searchSpaceId], queryFn: () => notesApiService.getNotes({ search_space_id: Number(searchSpaceId), page_size: 1000, }), enabled: !!searchSpaceId && open && !debouncedSearchQuery, }); // Search notes (when there's a search query) const { data: searchData, error: searchError, isLoading: isSearching, } = useQuery({ queryKey: ["search-notes", searchSpaceId, debouncedSearchQuery], queryFn: () => documentsApiService.searchDocuments({ queryParams: { search_space_id: Number(searchSpaceId), document_types: ["NOTE"], title: debouncedSearchQuery, page_size: 100, }, }), enabled: !!searchSpaceId && open && !!debouncedSearchQuery, }); // Handle note navigation const handleNoteClick = useCallback( (noteId: number, noteSearchSpaceId: number) => { router.push(`/dashboard/${noteSearchSpaceId}/editor/${noteId}`); onOpenChange(false); // Also close the main sidebar on mobile onCloseMobileSidebar?.(); }, [router, onOpenChange, onCloseMobileSidebar] ); // Handle note deletion const handleDeleteNote = useCallback( async (noteId: number, noteSearchSpaceId: number) => { setDeletingNoteId(noteId); try { await notesApiService.deleteNote({ search_space_id: noteSearchSpaceId, note_id: noteId, }); queryClient.invalidateQueries({ queryKey: ["all-notes", searchSpaceId] }); queryClient.invalidateQueries({ queryKey: ["notes", searchSpaceId] }); queryClient.invalidateQueries({ queryKey: ["search-notes", searchSpaceId] }); } catch (error) { console.error("Error deleting note:", error); } finally { setDeletingNoteId(null); } }, [queryClient, searchSpaceId] ); // Clear search const handleClearSearch = useCallback(() => { setSearchQuery(""); }, []); // Determine which data to show const isSearchMode = !!debouncedSearchQuery; const isLoading = isSearchMode ? isSearching : isLoadingNotes; const error = isSearchMode ? searchError : notesError; // Transform and sort notes data - handle both regular notes and search results const notes = useMemo(() => { let notesList: { id: number; title: string; search_space_id: number; created_at: string; updated_at?: string | null; }[]; if (isSearchMode && searchData?.items) { notesList = searchData.items.map((doc) => ({ id: doc.id, title: doc.title, search_space_id: doc.search_space_id, created_at: doc.created_at, updated_at: doc.updated_at, })); } else { notesList = notesData?.items ?? []; } // Sort notes by updated_at (most recent first), fallback to created_at return [...notesList].sort((a, b) => { const dateA = a.updated_at ? new Date(a.updated_at).getTime() : new Date(a.created_at).getTime(); const dateB = b.updated_at ? new Date(b.updated_at).getTime() : new Date(b.created_at).getTime(); return dateB - dateA; // Descending order (most recent first) }); }, [isSearchMode, searchData, notesData]); if (!mounted) return null; return createPortal( {open && ( <> {/* Backdrop */} onOpenChange(false)} aria-hidden="true" /> {/* Panel */} {/* Header */}

{t("all_notes") || "All Notes"}

{/* Search Input */}
setSearchQuery(e.target.value)} className="pl-9 pr-8 h-9" /> {searchQuery && ( )}
{/* Scrollable Content */}
{isLoading ? (
) : error ? (
{t("error_loading_notes") || "Error loading notes"}
) : notes.length > 0 ? (
{notes.map((note) => { const isDeleting = deletingNoteId === note.id; const isActive = currentNoteId === note.id; return (
{/* Main clickable area for navigation */}

{t("created") || "Created"}:{" "} {format(new Date(note.created_at), "MMM d, yyyy 'at' h:mm a")}

{note.updated_at && (

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

)}
{/* Actions dropdown - separate from main click area */} setOpenDropdownId(isOpen ? note.id : null)} > handleDeleteNote(note.id, note.search_space_id)} className="text-destructive focus:text-destructive" > {t("delete") || "Delete"}
); })}
) : isSearchMode ? (

{t("no_results_found") || "No notes found"}

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

) : (

{t("no_notes") || "No notes yet"}

{onAddNote && ( )}
)}
{/* Footer with Add Note button */} {onAddNote && notes.length > 0 && (
)}
)}
, document.body ); }