'use client' import { useState, useEffect, useCallback } from 'react' import { useSearchParams, useRouter } from 'next/navigation' import { Note } from '@/lib/types' import { getAllNotes, getPinnedNotes, getRecentNotes, searchNotes } from '@/app/actions/notes' import { getAISettings } from '@/app/actions/ai-settings' import { NoteInput } from '@/components/note-input' import { MasonryGrid } from '@/components/masonry-grid' import { MemoryEchoNotification } from '@/components/memory-echo-notification' import { NotebookSuggestionToast } from '@/components/notebook-suggestion-toast' import { NoteEditor } from '@/components/note-editor' import { BatchOrganizationDialog } from '@/components/batch-organization-dialog' import { AutoLabelSuggestionDialog } from '@/components/auto-label-suggestion-dialog' import { FavoritesSection } from '@/components/favorites-section' import { RecentNotesSection } from '@/components/recent-notes-section' import { Button } from '@/components/ui/button' import { Wand2 } from 'lucide-react' import { useLabels } from '@/context/LabelContext' import { useNoteRefresh } from '@/context/NoteRefreshContext' import { useReminderCheck } from '@/hooks/use-reminder-check' import { useAutoLabelSuggestion } from '@/hooks/use-auto-label-suggestion' import { useNotebooks } from '@/context/notebooks-context' import { Folder, Briefcase, FileText, Zap, BarChart3, Globe, Sparkles, Book, Heart, Crown, Music, Building2, Plane, ChevronRight, Plus } from 'lucide-react' import { cn } from '@/lib/utils' import { LabelFilter } from '@/components/label-filter' import { useLanguage } from '@/lib/i18n' export default function HomePage() { const searchParams = useSearchParams() const router = useRouter() const { t } = useLanguage() // Force re-render when search params change (for filtering) const [notes, setNotes] = useState([]) const [pinnedNotes, setPinnedNotes] = useState([]) const [recentNotes, setRecentNotes] = useState([]) const [showRecentNotes, setShowRecentNotes] = useState(false) const [editingNote, setEditingNote] = useState<{ note: Note; readOnly?: boolean } | null>(null) const [isLoading, setIsLoading] = useState(true) const [notebookSuggestion, setNotebookSuggestion] = useState<{ noteId: string; content: string } | null>(null) const [batchOrganizationOpen, setBatchOrganizationOpen] = useState(false) const { refreshKey } = useNoteRefresh() const { labels } = useLabels() // Auto label suggestion (IA4) const { shouldSuggest: shouldSuggestLabels, notebookId: suggestNotebookId, dismiss: dismissLabelSuggestion } = useAutoLabelSuggestion() const [autoLabelOpen, setAutoLabelOpen] = useState(false) // Open auto label dialog when suggestion is available useEffect(() => { if (shouldSuggestLabels && suggestNotebookId) { setAutoLabelOpen(true) } }, [shouldSuggestLabels, suggestNotebookId]) // Check if viewing Notes générales (no notebook filter) const notebookFilter = searchParams.get('notebook') const isInbox = !notebookFilter // Callback for NoteInput to trigger notebook suggestion and update UI const handleNoteCreated = useCallback((note: Note) => { // Update UI immediately by adding the note to the list if it matches current filters setNotes((prevNotes) => { // Check if note matches current filters const notebookFilter = searchParams.get('notebook') const labelFilter = searchParams.get('labels')?.split(',').filter(Boolean) || [] const colorFilter = searchParams.get('color') const search = searchParams.get('search')?.trim() || null // Check notebook filter if (notebookFilter && note.notebookId !== notebookFilter) { return prevNotes // Note doesn't match notebook filter } if (!notebookFilter && note.notebookId) { return prevNotes // Viewing inbox but note has notebook } // Check label filter if (labelFilter.length > 0) { const noteLabels = note.labels || [] if (!noteLabels.some((label: string) => labelFilter.includes(label))) { return prevNotes // Note doesn't match label filter } } // Check color filter if (colorFilter) { const labelNamesWithColor = labels .filter((label: any) => label.color === colorFilter) .map((label: any) => label.name) const noteLabels = note.labels || [] if (!noteLabels.some((label: string) => labelNamesWithColor.includes(label))) { return prevNotes // Note doesn't match color filter } } // Check search filter (simple check - if searching, let refresh handle it) if (search) { // If searching, refresh to get proper search results router.refresh() return prevNotes } // Note matches all filters - add it optimistically to the beginning of the list // (newest notes first based on order: isPinned desc, order asc, updatedAt desc) const isPinned = note.isPinned || false const pinnedNotes = prevNotes.filter(n => n.isPinned) const unpinnedNotes = prevNotes.filter(n => !n.isPinned) if (isPinned) { // Add to beginning of pinned notes return [note, ...pinnedNotes, ...unpinnedNotes] } else { // Add to beginning of unpinned notes return [...pinnedNotes, note, ...unpinnedNotes] } }) // Only suggest if note has no notebook and has 20+ words if (!note.notebookId) { const wordCount = (note.content || '').trim().split(/\s+/).filter(w => w.length > 0).length if (wordCount >= 20) { setNotebookSuggestion({ noteId: note.id, content: note.content || '' }) } else { } } else { } // Refresh in background to ensure data consistency (non-blocking) router.refresh() }, [searchParams, labels, router]) const handleOpenNote = (noteId: string) => { const note = notes.find(n => n.id === noteId) if (note) { setEditingNote({ note, readOnly: false }) } } // Enable reminder notifications useReminderCheck(notes) // Load user settings to check if recent notes should be shown useEffect(() => { const loadSettings = async () => { try { const settings = await getAISettings() setShowRecentNotes(settings.showRecentNotes === true) } catch (error) { setShowRecentNotes(false) } } loadSettings() }, [refreshKey]) useEffect(() => { const loadNotes = async () => { setIsLoading(true) const search = searchParams.get('search')?.trim() || null const semanticMode = searchParams.get('semantic') === 'true' const labelFilter = searchParams.get('labels')?.split(',').filter(Boolean) || [] const colorFilter = searchParams.get('color') const notebookFilter = searchParams.get('notebook') let allNotes = search ? await searchNotes(search, semanticMode, notebookFilter || undefined) : await getAllNotes() // Filter by selected notebook if (notebookFilter) { allNotes = allNotes.filter((note: any) => note.notebookId === notebookFilter) } else { // If no notebook selected, only show notes without notebook (Notes générales) allNotes = allNotes.filter((note: any) => !note.notebookId) } // Filter by selected labels if (labelFilter.length > 0) { allNotes = allNotes.filter((note: any) => note.labels?.some((label: string) => labelFilter.includes(label)) ) } // Filter by color (filter notes that have labels with this color) // Note: We use a ref-like pattern to avoid including labels in dependencies // This prevents dialog closing when adding new labels if (colorFilter) { const labelNamesWithColor = labels .filter((label: any) => label.color === colorFilter) .map((label: any) => label.name) allNotes = allNotes.filter((note: any) => note.labels?.some((label: string) => labelNamesWithColor.includes(label)) ) } // Load pinned notes separately (shown in favorites section) const pinned = await getPinnedNotes(notebookFilter || undefined) setPinnedNotes(pinned) // Load recent notes only if enabled in settings if (showRecentNotes) { const recent = await getRecentNotes(3) // Filter recent notes by current filters if (notebookFilter) { setRecentNotes(recent.filter((note: any) => note.notebookId === notebookFilter)) } else { setRecentNotes(recent.filter((note: any) => !note.notebookId)) } } else { setRecentNotes([]) } setNotes(allNotes) setIsLoading(false) } loadNotes() // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchParams, refreshKey, showRecentNotes]) // Intentionally omit 'labels' and 'semantic' to prevent reload when adding tags or from router.push // Get notebooks context to display header const { notebooks } = useNotebooks() const currentNotebook = notebooks.find((n: any) => n.id === searchParams.get('notebook')) const [showNoteInput, setShowNoteInput] = useState(false) // Get icon component for header const getNotebookIcon = (iconName: string) => { const ICON_MAP: Record = { 'folder': Folder, 'briefcase': Briefcase, 'document': FileText, 'lightning': Zap, 'chart': BarChart3, 'globe': Globe, 'sparkle': Sparkles, 'book': Book, 'heart': Heart, 'crown': Crown, 'music': Music, 'building': Building2, 'flight_takeoff': Plane, } return ICON_MAP[iconName] || Folder } // Handle Note Created to close the input if desired, or keep open const handleNoteCreatedWrapper = (note: any) => { handleNoteCreated(note) setShowNoteInput(false) } // Helper for Breadcrumbs const Breadcrumbs = ({ notebookName }: { notebookName: string }) => (
{t('nav.notebooks')} {notebookName}
) return (
{/* Notebook Specific Header */} {currentNotebook ? (
{/* Breadcrumbs */}
{/* Title Section */}
{(() => { const Icon = getNotebookIcon(currentNotebook.icon || 'folder') return ( ) })()}

{currentNotebook.name}

{/* Actions Section */}
{ const params = new URLSearchParams(searchParams.toString()) if (newLabels.length > 0) params.set('labels', newLabels.join(',')) else params.delete('labels') router.push(`/?${params.toString()}`) }} className="border-gray-200" />
) : ( /* Default Header for Home/Inbox */
{/* Breadcrumbs Placeholder or just spacing */}
{/* Title Section */}

{t('notes.title')}

{/* Actions Section */}
{ const params = new URLSearchParams(searchParams.toString()) if (newLabels.length > 0) params.set('labels', newLabels.join(',')) else params.delete('labels') router.push(`/?${params.toString()}`) }} className="border-gray-200" /> {/* AI Organization Button - Moved to Header */} {isInbox && !isLoading && notes.length >= 2 && ( )}
)} {/* Note Input - Conditionally Visible or Always Visible on Home */} {/* Note Input - Conditionally Rendered */} {showNoteInput && (
)} {isLoading ? (
{t('general.loading')}
) : ( <> {/* Favorites Section - Pinned Notes */} setEditingNote({ note, readOnly })} /> {/* Recent Notes Section - Only shown if enabled in settings */} {showRecentNotes && ( !note.isPinned)} onEdit={(note, readOnly) => setEditingNote({ note, readOnly })} /> )} {/* Main Notes Grid - Unpinned Notes Only */} {notes.filter(note => !note.isPinned).length > 0 && (
!note.isPinned)} onEdit={(note, readOnly) => setEditingNote({ note, readOnly })} />
)} {/* Empty state when no notes */} {notes.filter(note => !note.isPinned).length === 0 && pinnedNotes.length === 0 && (
{t('notes.emptyState')}
)} )} {/* Memory Echo - Proactive note connections */} {/* Notebook Suggestion - IA1 */} {notebookSuggestion && ( setNotebookSuggestion(null)} /> )} {/* Batch Organization Dialog - IA3 */} { // Refresh notes to see updated notebook assignments router.refresh() }} /> {/* Auto Label Suggestion Dialog - IA4 */} { setAutoLabelOpen(open) if (!open) dismissLabelSuggestion() }} notebookId={suggestNotebookId} onLabelsCreated={() => { // Refresh to see new labels router.refresh() }} /> {/* Note Editor Modal */} {editingNote && ( setEditingNote(null)} /> )}
) }