diff --git a/memento-note/components/sidebar.tsx b/memento-note/components/sidebar.tsx index 942b5f2..45cf235 100644 --- a/memento-note/components/sidebar.tsx +++ b/memento-note/components/sidebar.tsx @@ -17,10 +17,13 @@ import { MessageSquare, Sparkles, Trash2, + User, + LogOut, + Shield, } from 'lucide-react' import { useLanguage } from '@/lib/i18n' -import { useNoteRefreshOptional } from '@/context/NoteRefreshContext' -import { useEffect, useState } from 'react' +import { useNotebooksQuery } from '@/lib/query-hooks' +import { useEffect, useMemo, useState } from 'react' import { getAllNotes } from '@/app/actions/notes' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { useNotebooks } from '@/context/notebooks-context' @@ -29,6 +32,14 @@ import { motion, AnimatePresence } from 'motion/react' import { getNoteDisplayTitle } from '@/lib/note-preview' import { CreateNotebookDialog } from './create-notebook-dialog' import { NotificationPanel } from './notification-panel' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { signOut } from 'next-auth/react' type NavigationView = 'notebooks' | 'agents' type SortOrder = 'newest' | 'oldest' | 'alpha' @@ -71,10 +82,11 @@ function SidebarCarnetItem({ }: { carnet: { id: string; name: string; initial: string; isPrivate?: boolean } isActive: boolean + /** Notes for this carnet — always passed (like architectural-grid ref); visibility toggled by isActive */ notes: { id: string; title: string }[] activeNoteId: string | null onCarnetClick: () => void - onNoteClick: (noteId: string) => void + onNoteClick: (noteId: string, carnetId: string) => void }) { return (
@@ -127,7 +139,7 @@ function SidebarCarnetItem({ key={note.id} title={note.title} isActive={activeNoteId === note.id} - onClick={() => onNoteClick(note.id)} + onClick={() => onNoteClick(note.id, carnet.id)} /> ))} {notes.length === 0 && ( @@ -145,7 +157,6 @@ export function Sidebar({ className, user }: { className?: string; user?: any }) const searchParams = useSearchParams() const router = useRouter() const { t } = useLanguage() - const { refreshKey } = useNoteRefreshOptional() const { notebooks } = useNotebooks() const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false) const [notebookNotes, setNotebookNotes] = useState>({}) @@ -160,32 +171,48 @@ export function Sidebar({ className, user }: { className?: string; user?: any }) const isInboxActive = pathname === '/' && !searchParams.get('notebook') && - !searchParams.get('label') && + !searchParams.get('labels') && !searchParams.get('archived') && !searchParams.get('trashed') - // Sync activeView with current route + // Sync toggle with route (fixes staying on "Agents" tab after navigating home) useEffect(() => { - if (pathname.startsWith('/agents') || pathname.startsWith('/lab')) { - setActiveView('agents') - } + setActiveView( + pathname.startsWith('/agents') || pathname.startsWith('/lab') ? 'agents' : 'notebooks' + ) }, [pathname]) const displayName = user?.name || user?.email || '' const initial = displayName ? displayName.charAt(0).toUpperCase() : '?' - useEffect(() => { - if (!currentNotebookId) return - if (notebookNotes[currentNotebookId]) return + const notebookIdsKey = useMemo(() => notebooks.map(nb => nb.id).sort().join(','), [notebooks]) - getAllNotes(false, currentNotebookId).then(notes => { - const mapped = notes.map((n: Note) => ({ - id: n.id, - title: getNoteDisplayTitle(n, t('notes.untitled') || 'Untitled'), - })) - setNotebookNotes(prev => ({ ...prev, [currentNotebookId!]: mapped })) - }) - }, [currentNotebookId, refreshKey]) + /** Load note titles for every notebook (like ref: filter per carnet). + * Refetch when notebooks list changes (added/removed/reordered). + * Note: individual note changes (create/edit/delete) don't need to trigger this + * because React Query cache handles invalidation separately. */ + useEffect(() => { + if (!notebookIdsKey) return + let cancelled = false + const load = async () => { + const mappedEntries = await Promise.all( + notebooks.map(async (nb: Notebook) => { + const notes = await getAllNotes(false, nb.id) + const mapped = notes.map((n: Note) => ({ + id: n.id, + title: getNoteDisplayTitle(n, t('notes.untitled') || 'Untitled'), + })) + return [nb.id, mapped] as const + }) + ) + if (cancelled) return + setNotebookNotes(Object.fromEntries(mappedEntries)) + } + load() + return () => { + cancelled = true + } + }, [notebookIdsKey, notebooks, t]) // BUG FIX: clicking a carnet always forces list (editorial) view const handleCarnetClick = (notebookId: string) => { @@ -200,8 +227,9 @@ export function Sidebar({ className, user }: { className?: string; user?: any }) router.push('/?forceList=1') } - const handleNoteClick = (noteId: string) => { + const handleNoteClick = (noteId: string, notebookId: string) => { const params = new URLSearchParams(searchParams.toString()) + params.set('notebook', notebookId) params.set('openNote', noteId) params.delete('forceList') router.push(`/?${params.toString()}`) @@ -225,26 +253,63 @@ export function Sidebar({ className, user }: { className?: string; user?: any }) <>