Keep/keep-notes/components/notebooks-list.tsx

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}
/>
</>
)
}