244 lines
8.5 KiB
TypeScript
244 lines
8.5 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useCallback } from 'react'
|
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
|
import { cn } from '@/lib/utils'
|
|
import { StickyNote, Plus, Tag as TagIcon, Folder, Briefcase, FileText, Zap, BarChart3, Globe, Sparkles, Book, Heart, Crown, Music, Building2, LucideIcon } from 'lucide-react'
|
|
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'
|
|
|
|
// Map icon names to lucide-react components
|
|
const ICON_MAP: Record<string, LucideIcon> = {
|
|
'folder': Folder,
|
|
'briefcase': Briefcase,
|
|
'document': FileText,
|
|
'lightning': Zap,
|
|
'chart': BarChart3,
|
|
'globe': Globe,
|
|
'sparkle': Sparkles,
|
|
'book': Book,
|
|
'heart': Heart,
|
|
'crown': Crown,
|
|
'music': Music,
|
|
'building': Building2,
|
|
}
|
|
|
|
// Function to get icon component by name
|
|
const getNotebookIcon = (iconName: string) => {
|
|
const IconComponent = ICON_MAP[iconName] || Folder
|
|
return IconComponent
|
|
}
|
|
|
|
export function NotebooksList() {
|
|
const pathname = usePathname()
|
|
const searchParams = useSearchParams()
|
|
const router = useRouter()
|
|
const { t } = useLanguage()
|
|
const { notebooks, currentNotebook, deleteNotebook, moveNoteToNotebookOptimistic, isLoading } = useNotebooks()
|
|
const { draggedNoteId, dragOverNotebookId, dragOver } = useNotebookDrag()
|
|
|
|
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
|
|
const [editingNotebook, setEditingNotebook] = useState<any>(null)
|
|
const [deletingNotebook, setDeletingNotebook] = useState<any>(null)
|
|
const [summaryNotebook, setSummaryNotebook] = useState<any>(null) // NEW: Summary dialog state (IA6)
|
|
|
|
const currentNotebookId = searchParams.get('notebook')
|
|
|
|
// Handle drop on a notebook
|
|
const handleDrop = useCallback(async (e: React.DragEvent, notebookId: string | null) => {
|
|
e.preventDefault()
|
|
e.stopPropagation() // Prevent triggering notebook click
|
|
const noteId = e.dataTransfer.getData('text/plain')
|
|
|
|
if (noteId) {
|
|
await moveNoteToNotebookOptimistic(noteId, notebookId)
|
|
router.refresh() // Refresh the page to show the moved note
|
|
}
|
|
|
|
dragOver(null)
|
|
}, [moveNoteToNotebookOptimistic, dragOver, router])
|
|
|
|
// Handle drag over a notebook
|
|
const handleDragOver = useCallback((e: React.DragEvent, notebookId: string | null) => {
|
|
e.preventDefault()
|
|
dragOver(notebookId)
|
|
}, [dragOver])
|
|
|
|
// Handle drag leave
|
|
const handleDragLeave = useCallback(() => {
|
|
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()}`)
|
|
}
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="my-2">
|
|
<div className="px-4 mb-2">
|
|
<span className="text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
{t('nav.notebooks')}
|
|
</span>
|
|
</div>
|
|
<div className="px-4 py-2">
|
|
<div className="text-xs text-gray-500">{t('common.loading')}</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{/* Notebooks Section */}
|
|
<div className="my-2">
|
|
{/* Section Header */}
|
|
<div className="px-4 flex items-center justify-between mb-1">
|
|
<span className="text-xs font-medium text-slate-400 dark:text-slate-500 uppercase tracking-wider">
|
|
{t('nav.notebooks')}
|
|
</span>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-6 w-6 p-0 text-slate-400 hover:text-slate-600 dark:hover:text-slate-300"
|
|
onClick={() => setIsCreateDialogOpen(true)}
|
|
>
|
|
<Plus className="h-3 w-3" />
|
|
</Button>
|
|
</div>
|
|
|
|
{/* "Notes générales" (Inbox) */}
|
|
<button
|
|
onClick={() => handleSelectNotebook(null)}
|
|
onDrop={(e) => handleDrop(e, null)}
|
|
onDragOver={(e) => handleDragOver(e, null)}
|
|
onDragLeave={handleDragLeave}
|
|
className={cn(
|
|
"flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-200",
|
|
!currentNotebookId && pathname === '/' && !searchParams.get('search')
|
|
? "bg-[#FEF3C6] text-amber-900 shadow-lg"
|
|
: "text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800/50 hover:translate-x-1"
|
|
)}
|
|
>
|
|
<StickyNote className="h-5 w-5" />
|
|
<span className={cn("text-sm font-medium", !currentNotebookId && pathname === '/' && !searchParams.get('search') && "font-semibold")}>{t('nav.generalNotes')}</span>
|
|
</button>
|
|
|
|
{/* Notebooks List */}
|
|
{notebooks.map((notebook: any) => {
|
|
const isActive = currentNotebookId === notebook.id
|
|
const isDragOver = dragOverNotebookId === notebook.id
|
|
|
|
// Get the icon component
|
|
const NotebookIcon = getNotebookIcon(notebook.icon || 'folder')
|
|
|
|
return (
|
|
<div key={notebook.id} className="group flex items-center">
|
|
<button
|
|
onClick={() => handleSelectNotebook(notebook.id)}
|
|
onDrop={(e) => handleDrop(e, notebook.id)}
|
|
onDragOver={(e) => handleDragOver(e, notebook.id)}
|
|
onDragLeave={handleDragLeave}
|
|
className={cn(
|
|
"flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-200",
|
|
isActive
|
|
? "bg-[#FEF3C6] text-amber-900 shadow-lg"
|
|
: "text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800/50 hover:translate-x-1",
|
|
isDragOver && "ring-2 ring-blue-500 ring-dashed"
|
|
)}
|
|
>
|
|
{/* Icon with notebook color */}
|
|
<div
|
|
className="h-5 w-5 rounded flex items-center justify-center"
|
|
style={{
|
|
backgroundColor: isActive ? 'white' : notebook.color || '#6B7280',
|
|
color: isActive ? (notebook.color || '#6B7280') : 'white'
|
|
}}
|
|
>
|
|
<NotebookIcon className="h-3 w-3" />
|
|
</div>
|
|
<span className={cn("truncate flex-1 text-left text-sm", isActive && "font-semibold")}>{notebook.name}</span>
|
|
{notebook.notesCount > 0 && (
|
|
<span className={cn(
|
|
"ml-auto text-[10px] font-medium px-1.5 py-0.5 rounded",
|
|
isActive
|
|
? "bg-amber-900/20 text-amber-900"
|
|
: "text-gray-500"
|
|
)}>
|
|
{notebook.notesCount}
|
|
</span>
|
|
)}
|
|
</button>
|
|
|
|
{/* Actions (visible on hover) */}
|
|
<NotebookActions
|
|
notebook={notebook}
|
|
onEdit={() => setEditingNotebook(notebook)}
|
|
onDelete={() => setDeletingNotebook(notebook)}
|
|
onSummary={() => setSummaryNotebook(notebook)} // NEW: Summary action (IA6)
|
|
/>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
{/* Create Notebook Dialog */}
|
|
<CreateNotebookDialog
|
|
open={isCreateDialogOpen}
|
|
onOpenChange={setIsCreateDialogOpen}
|
|
/>
|
|
|
|
{/* Edit Notebook Dialog */}
|
|
{editingNotebook && (
|
|
<EditNotebookDialog
|
|
notebook={editingNotebook}
|
|
open={!!editingNotebook}
|
|
onOpenChange={(open) => {
|
|
if (!open) setEditingNotebook(null)
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{/* Delete Confirmation Dialog */}
|
|
{deletingNotebook && (
|
|
<DeleteNotebookDialog
|
|
notebook={deletingNotebook}
|
|
open={!!deletingNotebook}
|
|
onOpenChange={(open) => {
|
|
if (!open) setDeletingNotebook(null)
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{/* Notebook Summary Dialog (IA6) */}
|
|
<NotebookSummaryDialog
|
|
open={!!summaryNotebook}
|
|
onOpenChange={(open) => {
|
|
if (!open) setSummaryNotebook(null)
|
|
}}
|
|
notebookId={summaryNotebook?.id}
|
|
notebookName={summaryNotebook?.name}
|
|
/>
|
|
</>
|
|
)
|
|
}
|