From 6e13b6d207aa40462092784d0a145ff48833716a Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sun, 5 Jul 2026 18:54:41 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20note=20supprim=C3=A9e=20depuis=20=C3=A9d?= =?UTF-8?q?iteur=20=E2=80=94=20dispara=C3=AEt=20de=20la=20liste=20sans=20r?= =?UTF-8?q?efresh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: NOTE_CHANGE_EVENT listener dans home-client ne gérait que 'updated' Quand deleteNote émettait { type: 'deleted' }, l'événement était ignoré. Fix: listener gère maintenant 'deleted' (removeNoteFromList) + 'created' --- memento-note/components/home-client.tsx | 5 + memento-note/components/sidebar-panels.tsx | 348 +++++++++++++++++++++ 2 files changed, 353 insertions(+) create mode 100644 memento-note/components/sidebar-panels.tsx diff --git a/memento-note/components/home-client.tsx b/memento-note/components/home-client.tsx index ec096f8..d5aa37e 100644 --- a/memento-note/components/home-client.tsx +++ b/memento-note/components/home-client.tsx @@ -619,6 +619,11 @@ export function HomeClient({ if (detail.type === 'updated') { patchNoteInList(detail.note.id, detail.note) setPinnedNotes(prev => prev.map(n => n.id === detail.note.id ? { ...n, ...detail.note } : n)) + } else if (detail.type === 'deleted') { + removeNoteFromList(detail.noteId) + setPinnedNotes(prev => prev.filter(n => n.id !== detail.noteId)) + } else if (detail.type === 'created' && detail.note) { + setNotes(prev => [detail.note, ...prev]) } } window.addEventListener(NOTE_CHANGE_EVENT, onNoteChange) diff --git a/memento-note/components/sidebar-panels.tsx b/memento-note/components/sidebar-panels.tsx new file mode 100644 index 0000000..09ce8fa --- /dev/null +++ b/memento-note/components/sidebar-panels.tsx @@ -0,0 +1,348 @@ +'use client' + +import { useRouter } from 'next/navigation' +import { cn } from '@/lib/utils' +import { useLanguage } from '@/lib/i18n' +import React, { useEffect, useState } from 'react' +import { motion, AnimatePresence } from 'motion/react' +import { getAllNotes, getNotesWithReminders, toggleReminderDone } from '@/app/actions/notes' +import { useBrainstormSessions, useDeleteBrainstorm } from '@/hooks/use-brainstorm' +import { + ChevronRight, Lock, Plus, Pencil, Trash2, Sparkles, Clock, + Pin, PinOff, GripVertical, FileText, Folder, FolderOpen, +} from 'lucide-react' + +export function NoteLink({ + title, isActive, isPinned, onClick, +}: { + title: string; isActive: boolean; isPinned?: boolean; onClick: () => void +}) { + const { language } = useLanguage() + const slideX = language === 'fa' || language === 'ar' ? 10 : -10 + return ( + + + {title} + {isPinned && } + + ) +} + +export function SidebarBrainstorms() { + const { data: sessions, isLoading } = useBrainstormSessions() + const deleteBrainstorm = useDeleteBrainstorm() + const router = useRouter() + const { t } = useLanguage() + + if (isLoading) { + return ( +
+ {[1, 2, 3].map(i =>
)} +
+ ) + } + + if (!sessions || sessions.length === 0) { + return ( +
+ +

{t('brainstorm.noSessions')}

+ +
+ ) + } + + return ( +
+ {sessions.slice(0, 10).map(s => ( +
+ + {(s as any)._owned !== false && ( + + )} +
+ ))} +
+ ) +} + +export function SidebarReminders({ onOpenNote }: { onOpenNote: (noteId: string, notebookId: string | null) => void }) { + const { t } = useLanguage() + const [reminders, setReminders] = useState< + { id: string; title: string | null; reminder: Date | string | null; isReminderDone: boolean; notebookId: string | null }[] + >([]) + const [loading, setLoading] = useState(true) + const [togglingId, setTogglingId] = useState(null) + + const reload = () => { getNotesWithReminders().then((rows) => setReminders(rows as typeof reminders)) } + + useEffect(() => { + let cancelled = false + setLoading(true) + getNotesWithReminders().then((rows) => { if (!cancelled) setReminders(rows as typeof reminders) }).finally(() => { if (!cancelled) setLoading(false) }) + return () => { cancelled = true } + }, []) + + const handleComplete = async (e: React.MouseEvent, noteId: string) => { + e.stopPropagation(); setTogglingId(noteId) + await toggleReminderDone(noteId, true) + setReminders(prev => prev.map(r => r.id === noteId ? { ...r, isReminderDone: true } : r)) + setTogglingId(null) + } + + const handleSnooze = async (e: React.MouseEvent, noteId: string) => { + e.stopPropagation() + const snoozeDate = new Date(Date.now() + 60 * 60 * 1000) + setTogglingId(noteId) + try { + await fetch(`/api/notes/${noteId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ reminder: snoozeDate.toISOString(), isReminderDone: false }) }) + setReminders(prev => prev.map(r => r.id === noteId ? { ...r, reminder: snoozeDate, isReminderDone: false } : r)) + } catch (e) { console.error('[silent-catch]', e); reload() } + setTogglingId(null) + } + + if (loading) { + return ( +
+ {[1, 2, 3].map((i) =>
)} +
+ ) + } + + const now = new Date() + const active = reminders.filter((r) => !r.isReminderDone && r.reminder) + const overdue = active.filter((r) => new Date(r.reminder!) < now) + const upcoming = active.filter((r) => new Date(r.reminder!) >= now) + + if (active.length === 0) { + return ( +
+ +

{t('reminders.emptyDescription')}

+
+ ) + } + + const renderItem = (note: (typeof active)[0], overdueItem?: boolean) => ( +
+ + +
+ +
+
+ ) + + return ( +
+ {overdue.length > 0 && ( +
+

{t('reminders.overdue')}

+
{overdue.map((n) => renderItem(n, true))}
+
+ )} + {upcoming.length > 0 && ( +
+

{t('reminders.upcoming')}

+
{upcoming.map((n) => renderItem(n))}
+
+ )} +
+ ) +} + +export function SidebarCarnetItem({ + carnet, isActive, notes, activeNoteId, onCarnetClick, onNoteClick, onAddSubNotebook, onRename, onDelete, onTogglePin, isPinned, + children, isDragging, dragHandleProps, level, isExpanded, toggleExpand, hasChildNotebooks, hasActiveDescendant, +}: { + carnet: { id: string; name: string; initial: string; isPrivate?: boolean } + isActive: boolean + notes: { id: string; title: string; isPinned?: boolean }[] + activeNoteId: string | null + onCarnetClick: () => void + onNoteClick: (noteId: string, carnetId: string) => void + onAddSubNotebook: () => void + onRename: () => void + onDelete: () => void + onTogglePin: () => void + isPinned: boolean + children?: React.ReactNode + isDragging?: boolean + dragHandleProps?: React.HTMLAttributes + level: number + isExpanded: boolean + toggleExpand: () => void + hasChildNotebooks?: boolean + hasActiveDescendant?: boolean +}) { + const { t, language } = useLanguage() + const isRtl = language === 'fa' || language === 'ar' + const hasChildren = hasChildNotebooks || React.Children.count(children) > 0 || notes.length > 0 + const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null) + + useEffect(() => { + if (!contextMenu) return + const handler = () => setContextMenu(null) + window.addEventListener('click', handler) + return () => window.removeEventListener('click', handler) + }, [contextMenu]) + + return ( +
+
{ e.preventDefault(); e.stopPropagation(); setContextMenu({ x: e.clientX, y: e.clientY }) }} + > + {level > 0 &&
} + {level > 0 &&
} +
+
e.stopPropagation()}> + +
+ {hasChildren ? ( + + ) :
} + { e.stopPropagation(); onRename() }} + className={cn( + 'flex-1 flex items-center gap-2.5 px-2 py-1.5 rounded-lg transition-all duration-300 group/item cursor-pointer relative overflow-hidden', + isActive ? 'bg-white dark:bg-white/10 shadow-sm border border-border/40' : 'hover:bg-white/40 dark:hover:bg-white/5' + )}> + {isActive && } +
+ {isExpanded ? : } +
+
+ + {carnet.name} + + {carnet.isPrivate && } + {!isActive && hasActiveDescendant && } +
+ {notes.length > 0 && ( + + {notes.length} + + )} +
+ {isPinned && } + + + +
+
+
+
+ + {contextMenu && ( + e.stopPropagation()} + > + +
+ + +
+ + + )} + + + {isExpanded && ( + +
+
+
+ {children} + {isExpanded && notes.map(note => ( + onNoteClick(note.id, carnet.id)} /> + ))} + {isExpanded && notes.length === 0 && !hasChildren && ( +

{t('sidebar.notebookEmpty')}

+ )} +
+
+ + )} + +
+ ) +}