'use client'; import { useState, useEffect, useCallback } from 'react'; import { Note } from '@/services/notes'; import { Folder } from '@/services/folders'; import { Task } from '@/lib/types'; import { notesClient } from '@/clients/notes'; import { NotesList } from '@/components/notes/NotesList'; import { MarkdownEditor } from '@/components/notes/MarkdownEditor'; import { FoldersSidebar } from '@/components/notes/FoldersSidebar'; import { Header } from '@/components/ui/Header'; import { Card } from '@/components/ui'; import { TasksProvider, useTasksContext } from '@/contexts/TasksContext'; import { Tag } from '@/lib/types'; import { FileText, AlertCircle, ChevronLeft, ChevronRight } from 'lucide-react'; import { getFolders } from '@/actions/folders'; import { reorderNotes } from '@/actions/notes'; interface NotesPageClientProps { initialNotes: Note[]; initialTags: (Tag & { usage: number })[]; initialFolders: Folder[]; } function NotesPageContent({ initialNotes, initialFolders, }: { initialNotes: Note[]; initialFolders: Folder[]; }) { const [notes, setNotes] = useState(initialNotes); const [folders, setFolders] = useState(initialFolders); const [selectedNote, setSelectedNote] = useState(null); const [selectedFolderId, setSelectedFolderId] = useState(null); const [isNewNote, setIsNewNote] = useState(false); const { tags: availableTags } = useTasksContext(); const [error, setError] = useState(null); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const glassSurface = 'bg-[var(--card)]/40 backdrop-blur-md relative before:absolute before:inset-0 before:bg-gradient-to-r before:from-[color-mix(in_srgb,var(--primary)_8%,transparent)] before:via-[color-mix(in_srgb,var(--primary)_4%,transparent)] before:to-transparent before:opacity-80 before:pointer-events-none'; const glassDivider = `${glassSurface} border-b border-[var(--border)]/60`; // Select first note if none selected useEffect(() => { if (notes.length > 0 && !selectedNote) { setSelectedNote(notes[0]); } }, [notes, selectedNote]); const handleSelectNote = useCallback( (note: Note) => { // Check for unsaved changes before switching if (hasUnsavedChanges && selectedNote) { const shouldSwitch = window.confirm( 'Vous avez des modifications non sauvegardées. Voulez-vous continuer sans sauvegarder ?' ); if (!shouldSwitch) return; } setSelectedNote(note); setIsNewNote(false); setHasUnsavedChanges(false); }, [hasUnsavedChanges, selectedNote] ); const handleCreateNote = useCallback(async () => { try { // Déterminer le folderId : null si "Toutes les notes" ou "Non classées", sinon le dossier actif const targetFolderId = !selectedFolderId || selectedFolderId === '__uncategorized__' ? undefined : selectedFolderId; console.log( '[handleCreateNote] selectedFolderId:', selectedFolderId, 'targetFolderId:', targetFolderId ); const newNote = await notesClient.createNote({ title: 'Nouvelle note', content: '# Nouvelle note\n\nCommencez à écrire...', folderId: targetFolderId, }); setNotes((prev) => [newNote, ...prev]); setSelectedNote(newNote); setIsNewNote(true); setHasUnsavedChanges(false); } catch (err) { setError('Erreur lors de la création de la note'); console.error('Error creating note:', err); } }, [selectedFolderId]); const handleDeleteNote = useCallback( async (noteId: string) => { try { await notesClient.deleteNote(noteId); setNotes((prev) => prev.filter((note) => note.id !== noteId)); // If deleted note was selected, select another one if (selectedNote?.id === noteId) { const remainingNotes = notes.filter((note) => note.id !== noteId); setSelectedNote(remainingNotes.length > 0 ? remainingNotes[0] : null); } } catch (err) { setError('Erreur lors de la suppression de la note'); console.error('Error deleting note:', err); } }, [selectedNote, notes] ); const reloadNotes = useCallback(async () => { try { const updatedNotes = await notesClient.getNotes(); setNotes(updatedNotes); } catch (err) { console.error('Error reloading notes:', err); } }, []); const handleReorderNotes = useCallback( async (noteOrders: Array<{ id: string; order: number }>) => { try { // Optimistic update const notesMap = new Map(notes.map((n) => [n.id, n])); const reorderedNotes = noteOrders .map((no) => notesMap.get(no.id)) .filter((n): n is Note => n !== undefined); setNotes(reorderedNotes); // Server update const result = await reorderNotes(noteOrders); if (!result.success) { throw new Error(result.error); } } catch (err) { setError('Erreur lors du réordonnancement des notes'); console.error('Error reordering notes:', err); // Reload notes on error reloadNotes(); } }, [notes, reloadNotes] ); const getNoteTitle = (content: string): string => { // Extract title from first line, removing markdown headers const firstLine = content.split('\n')[0] || ''; const title = firstLine .replace(/^#{1,6}\s+/, '') // Remove markdown headers .replace(/\*\*(.*?)\*\*/g, '$1') // Remove bold .replace(/\*(.*?)\*/g, '$1') // Remove italic .replace(/`(.*?)`/g, '$1') // Remove inline code .trim(); return title || 'Note sans titre'; }; const handleContentChange = useCallback( (content: string) => { if (selectedNote) { setSelectedNote((prev) => (prev ? { ...prev, content } : null)); setHasUnsavedChanges(true); } }, [selectedNote] ); const handleSave = useCallback(async () => { if (!selectedNote) return; try { // setIsSaving(true); setError(null); // Construire l'objet de mise à jour en incluant explicitement les champs const updateData: { content: string; tags?: string[]; taskId?: string; folderId?: string | null; } = { content: selectedNote.content, tags: selectedNote.tags, }; // Ajouter taskId et folderId seulement s'ils sont définis if (selectedNote.taskId !== undefined) { updateData.taskId = selectedNote.taskId; } if (selectedNote.folderId !== undefined) { updateData.folderId = selectedNote.folderId; } const updatedNote = await notesClient.updateNote( selectedNote.id, updateData ); // Mettre à jour la liste des notes mais pas selectedNote pour éviter la perte de focus setNotes((prev) => prev.map((note) => (note.id === selectedNote.id ? updatedNote : note)) ); setHasUnsavedChanges(false); setIsNewNote(false); } catch (err) { setError('Erreur lors de la sauvegarde'); console.error('Error saving note:', err); } finally { // setIsSaving(false); } }, [selectedNote]); const handleTagsChange = useCallback( (tags: string[]) => { if (!selectedNote) return; setSelectedNote((prev) => (prev ? { ...prev, tags } : null)); setHasUnsavedChanges(true); }, [selectedNote] ); const handleTaskChange = useCallback( (task: Task | null) => { if (!selectedNote) return; setSelectedNote((prev) => prev ? { ...prev, taskId: task?.id, task } : null ); setHasUnsavedChanges(true); }, [selectedNote] ); const handleFolderChange = useCallback( async (folderId: string | null) => { if (!selectedNote) return; try { // Sauvegarder immédiatement const updateData: { folderId: string | null } = { folderId: folderId, }; const updatedNote = await notesClient.updateNote( selectedNote.id, updateData ); // Mettre à jour la liste des notes setNotes((prev) => prev.map((note) => (note.id === selectedNote.id ? updatedNote : note)) ); // Mettre à jour la note sélectionnée setSelectedNote(updatedNote); } catch (err) { console.error('Error changing folder:', err); setError('Erreur lors du changement de dossier'); } }, [selectedNote] ); // Auto-save quand les tags changent useEffect(() => { if (hasUnsavedChanges && selectedNote) { const timeoutId = setTimeout(() => { handleSave(); }, 1000); // Sauvegarder après 1 seconde d'inactivité return () => clearTimeout(timeoutId); } }, [selectedNote, hasUnsavedChanges, handleSave]); // Filtrer les notes par dossier const filteredNotes = selectedFolderId === '__uncategorized__' ? notes.filter((note) => !note.folderId) // Notes sans dossier : selectedFolderId ? notes.filter((note) => note.folderId === selectedFolderId) : notes; // Toutes les notes // Gérer le changement de dossier const handleSelectFolder = useCallback( (folderId: string | null) => { setSelectedFolderId(folderId); // Sélectionner automatiquement la première note du dossier const folderNotes = folderId === '__uncategorized__' ? notes.filter((note) => !note.folderId) : folderId ? notes.filter((note) => note.folderId === folderId) : notes; if (folderNotes.length > 0) { setSelectedNote(folderNotes[0]); } else { setSelectedNote(null); } }, [notes] ); // Recharger les dossiers const handleFoldersChange = useCallback(async () => { try { const result = await getFolders(); if (result.success && result.data) { setFolders(result.data); } } catch (err) { console.error('Error fetching folders:', err); } }, []); // Gérer le drop d'une note sur un dossier const handleNoteDrop = useCallback( async (noteId: string, folderId: string | null) => { try { const updateData: { folderId: string | null } = { folderId: folderId, }; const updatedNote = await notesClient.updateNote(noteId, updateData); // Mettre à jour la liste des notes setNotes((prev) => prev.map((note) => (note.id === noteId ? updatedNote : note)) ); // Si la note sélectionnée est celle qu'on déplace, la mettre à jour aussi if (selectedNote?.id === noteId) { setSelectedNote(updatedNote); } // Recharger les dossiers pour mettre à jour les compteurs handleFoldersChange(); } catch (err) { console.error('Error moving note:', err); setError('Erreur lors du déplacement de la note'); } }, [selectedNote, handleFoldersChange] ); return (
{/* Combined Sidebar: Folders + Notes List */}
{sidebarCollapsed ? (
NOTES
) : ( <> {/* Header */}

Notes

{/* Folders Section */}
{/* Notes List */}
)}
{/* Main Editor Area */}
{error && (
{error}
)} {selectedNote ? ( <> {/* Note Header */}

{getNoteTitle(selectedNote.content)}

{selectedNote.folderId && (() => { const findFolder = ( foldersList: typeof folders ): (typeof folders)[0] | null => { for (const folder of foldersList) { if (folder.id === selectedNote.folderId) return folder; if (folder.children) { const found = findFolder(folder.children); if (found) return found; } } return null; }; const currentFolder = findFolder(folders); const folderTag = currentFolder?.tagId ? availableTags.find( (t) => t.id === currentFolder.tagId ) : null; return currentFolder ? (
📁 {currentFolder.name} {folderTag && (
{folderTag.name}
)}
) : null; })()}
{hasUnsavedChanges && ( Non sauvegardé )} Modifié{' '} {new Date(selectedNote.updatedAt).toLocaleDateString( 'fr-FR' )}
{/* Editor */}
setSidebarCollapsed(!sidebarCollapsed) } initialIsEditing={isNewNote} onEditingChange={(isEditing) => { if (!isEditing) { setIsNewNote(false); } }} className="h-full" />
) : (

Aucune note sélectionnée

Sélectionnez une note dans la liste ou créez-en une nouvelle

)}
); } export function NotesPageClient({ initialNotes, initialTags, initialFolders, }: NotesPageClientProps) { return ( ); }