'use client' import { useState, useCallback, useRef } from 'react' import Link from 'next/link' import { usePathname, useRouter, useSearchParams } from 'next/navigation' import { cn } from '@/lib/utils' import { StickyNote, Plus, Tag, Folder, ChevronDown, ChevronRight, GripVertical } from 'lucide-react' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { useNotebooks } from '@/context/notebooks-context' import { useNotebookDrag } from '@/context/notebook-drag-context' import { Button } from '@/components/ui/button' import { CreateNotebookDialog } from './create-notebook-dialog' import { NotebookActions } from './notebook-actions' import { DeleteNotebookDialog } from './delete-notebook-dialog' import { EditNotebookDialog } from './edit-notebook-dialog' import { NotebookSummaryDialog } from './notebook-summary-dialog' import { useLanguage } from '@/lib/i18n' import { useLabels } from '@/context/LabelContext' import { LabelManagementDialog } from '@/components/label-management-dialog' import { Notebook } from '@/lib/types' import { getNotebookIcon } from '@/lib/notebook-icon' function NotebookName({ children }: { name: string; children: React.ReactNode }) { return ( {children} ) } export function NotebooksList() { const pathname = usePathname() const searchParams = useSearchParams() const router = useRouter() const { t, language } = useLanguage() const { notebooks, currentNotebook, deleteNotebook, moveNoteToNotebookOptimistic, updateNotebookOrderOptimistic, isLoading } = useNotebooks() const { draggedNoteId, dragOverNotebookId, dragOver } = useNotebookDrag() const { labels } = useLabels() const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false) const [editingNotebook, setEditingNotebook] = useState(null) const [deletingNotebook, setDeletingNotebook] = useState(null) const [summaryNotebook, setSummaryNotebook] = useState(null) const [expandedNotebook, setExpandedNotebook] = useState(null) const [labelsDialogOpen, setLabelsDialogOpen] = useState(false) // ── Notebook reorder drag state ── const draggingNbRef = useRef(null) const [draggingNbId, setDraggingNbId] = useState(null) const [overNbId, setOverNbId] = useState(null) const currentNotebookId = searchParams.get('notebook') // Handle drop on a notebook (note-to-notebook OR notebook reorder) const handleDrop = useCallback(async (e: React.DragEvent, notebookId: string | null) => { e.preventDefault() e.stopPropagation() const sourceNbId = e.dataTransfer.getData('application/x-notebook') const noteId = e.dataTransfer.getData('text/plain') if (sourceNbId && notebookId && sourceNbId !== notebookId) { // ── Reorder notebooks ── const currentIds = notebooks.map((nb: Notebook) => nb.id) const fromIdx = currentIds.indexOf(sourceNbId) const toIdx = currentIds.indexOf(notebookId) if (fromIdx !== -1 && toIdx !== -1) { const newIds = [...currentIds] newIds.splice(fromIdx, 1) newIds.splice(toIdx, 0, sourceNbId) await updateNotebookOrderOptimistic(newIds) } } else if (noteId) { // ── Move note to notebook ── await moveNoteToNotebookOptimistic(noteId, notebookId) } dragOver(null) draggingNbRef.current = null setDraggingNbId(null) setOverNbId(null) }, [notebooks, moveNoteToNotebookOptimistic, updateNotebookOrderOptimistic, dragOver]) // Handle drag over a notebook const handleDragOver = useCallback((e: React.DragEvent, notebookId: string | null) => { e.preventDefault() if (draggingNbRef.current) { // Notebook reorder mode — just track the insertion target setOverNbId(notebookId) } else { // Note-to-notebook mode dragOver(notebookId) } }, [dragOver]) // Handle drag leave const handleDragLeave = useCallback(() => { if (draggingNbRef.current) { setOverNbId(null) } else { dragOver(null) } }, [dragOver]) // ── Notebook reorder handlers ── const handleNotebookDragStart = useCallback((e: React.DragEvent, notebookId: string) => { e.dataTransfer.setData('application/x-notebook', notebookId) e.dataTransfer.effectAllowed = 'move' draggingNbRef.current = notebookId // Slight delay so the drag ghost renders before opacity change setTimeout(() => setDraggingNbId(notebookId), 0) }, []) const handleNotebookDragEnd = useCallback(() => { draggingNbRef.current = null setDraggingNbId(null) setOverNbId(null) dragOver(null) }, [dragOver]) const handleSelectNotebook = (notebookId: string | null) => { const params = new URLSearchParams(searchParams) if (notebookId) { params.set('notebook', notebookId) } else { params.delete('notebook') } // Clear other filters params.delete('labels') params.delete('search') router.push(`/?${params.toString()}`) } const handleToggleExpand = (notebookId: string) => { setExpandedNotebook((prev) => (prev === notebookId ? null : notebookId)) } const handleLabelFilter = (labelName: string, notebookId: string) => { const params = new URLSearchParams(searchParams) const currentLabels = params.get('labels')?.split(',').filter(Boolean) || [] if (currentLabels.includes(labelName)) { params.set('labels', currentLabels.filter((l: string) => l !== labelName).join(',')) } else { params.set('labels', [...currentLabels, labelName].join(',')) } params.set('notebook', notebookId) router.push(`/?${params.toString()}`) } if (isLoading) { return (
{t('common.loading')}
) } return ( <>
{/* Header with Add Button */}
{t('nav.notebooks') || 'NOTEBOOKS'}
{/* Notebooks Loop */} {notebooks.map((notebook: Notebook) => { const isActive = currentNotebookId === notebook.id const isExpanded = expandedNotebook === notebook.id const isDragOver = dragOverNotebookId === notebook.id // Get icon component const NotebookIcon = getNotebookIcon(notebook.icon || 'folder') return (
handleNotebookDragStart(e, notebook.id)} onDragEnd={handleNotebookDragEnd} > {/* Insertion indicator above this notebook when reordering */} {overNbId === notebook.id && draggingNbId !== notebook.id && (
)} {isActive ? ( // Active notebook with expanded labels
handleDrop(e, notebook.id)} onDragOver={(e) => handleDragOver(e, notebook.id)} onDragLeave={handleDragLeave} className={cn( "flex flex-col me-2 rounded-e-full transition-all relative", !notebook.color && "bg-primary/10 dark:bg-primary/20", isDragOver && "ring-2 ring-primary ring-dashed" )} style={notebook.color ? { backgroundColor: `${notebook.color}20` } : undefined} > {/* Header */}
{notebook.name}
{/* Actions menu for active notebook */} setEditingNotebook(notebook)} onDelete={() => setDeletingNotebook(notebook)} onSummary={() => setSummaryNotebook(notebook)} />
{/* Contextual Labels Tree */} {isExpanded && (
{labels.length === 0 ? (

{t('sidebar.noLabelsInNotebook')}

) : ( labels.map((label: any) => ( )) )}
)}
) : ( // Inactive notebook
handleDrop(e, notebook.id)} onDragOver={(e) => handleDragOver(e, notebook.id)} onDragLeave={handleDragLeave} className={cn( "flex items-center relative", isDragOver && "ring-2 ring-primary ring-dashed rounded-e-full me-2" )} > {notebook.name} {/* Actions + expand on the right — always rendered, visible on hover */}
setEditingNotebook(notebook)} onDelete={() => setDeletingNotebook(notebook)} onSummary={() => setSummaryNotebook(notebook)} />
)}
) })}
{/* Create Notebook Dialog */} {/* Edit Notebook Dialog */} {editingNotebook && ( { if (!open) setEditingNotebook(null) }} /> )} {/* Delete Confirmation Dialog */} {deletingNotebook && ( { if (!open) setDeletingNotebook(null) }} /> )} {/* Notebook Summary Dialog */} { if (!open) setSummaryNotebook(null) }} notebookId={summaryNotebook?.id ?? null} notebookName={summaryNotebook?.name} /> ) }