'use client' import { useState, useTransition, useOptimistic } from 'react' import { Note } from '@/lib/types' import { Clock, Pin, FolderOpen, Trash2, Folder, X } from 'lucide-react' import { cn } from '@/lib/utils' import { useLanguage } from '@/lib/i18n' import { Button } from './ui/button' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from './ui/dropdown-menu' import { togglePin, deleteNote, dismissFromRecent } from '@/app/actions/notes' import { useRouter } from 'next/navigation' import { useNotebooks } from '@/context/notebooks-context' import { useNoteRefresh } from '@/context/NoteRefreshContext' import { toast } from 'sonner' import { StickyNote } from 'lucide-react' interface RecentNotesSectionProps { recentNotes: Note[] onEdit?: (note: Note, readOnly?: boolean) => void } export function RecentNotesSection({ recentNotes, onEdit }: RecentNotesSectionProps) { const { t } = useLanguage() const topThree = recentNotes.slice(0, 3) if (topThree.length === 0) return null return (
{t('notes.recent')} ยท {topThree.length}
{topThree.map((note, index) => ( ))}
) } function CompactCard({ note, index, onEdit }: { note: Note index: number onEdit?: (note: Note, readOnly?: boolean) => void }) { const { t } = useLanguage() const router = useRouter() const { notebooks, moveNoteToNotebookOptimistic } = useNotebooks() const { triggerRefresh } = useNoteRefresh() const [isDeleting, setIsDeleting] = useState(false) const [showNotebookMenu, setShowNotebookMenu] = useState(false) const [, startTransition] = useTransition() // Optimistic UI state const [optimisticNote, addOptimisticNote] = useOptimistic( note, (state: Note, newProps: Partial) => ({ ...state, ...newProps }) ) const timeAgo = getCompactTime(optimisticNote.contentUpdatedAt || optimisticNote.updatedAt, t) const isFirstNote = index === 0 const handleTogglePin = async (e: React.MouseEvent) => { e.stopPropagation() startTransition(async () => { const newPinnedState = !optimisticNote.isPinned addOptimisticNote({ isPinned: newPinnedState }) await togglePin(note.id, newPinnedState) // Trigger global refresh to update lists triggerRefresh() router.refresh() if (newPinnedState) { toast.success(t('notes.pinned') || 'Note pinned') } else { toast.info(t('notes.unpinned') || 'Note unpinned') } }) } const handleMoveToNotebook = async (notebookId: string | null) => { await moveNoteToNotebookOptimistic(note.id, notebookId) setShowNotebookMenu(false) triggerRefresh() } const handleDelete = async (e: React.MouseEvent) => { e.stopPropagation() toast(t('notes.confirmDelete'), { action: { label: t('notes.delete'), onClick: async () => { setIsDeleting(true) try { await deleteNote(note.id) triggerRefresh() router.refresh() } catch (error) { console.error('Failed to delete note:', error) setIsDeleting(false) } }, }, cancel: { label: t('common.cancel'), onClick: () => {}, }, duration: 5000, }) } const handleDismiss = async (e: React.MouseEvent) => { e.stopPropagation() // Optimistic removal setIsDeleting(true) try { await dismissFromRecent(note.id) // Don't refresh list to prevent immediate replacement // triggerRefresh() // router.refresh() toast.success(t('notes.dismissed') || 'Note dismissed from recent') } catch (error) { console.error('Failed to dismiss note:', error) setIsDeleting(false) toast.error(t('common.error') || 'Failed to dismiss') } } if (isDeleting) return null return (
onEdit?.(note)} >
{/* Action Buttons Overlay - Ensure visible on hover and touch devices often handle this with tap */}
{/* Pin Button */} {/* Move to Notebook */} e.stopPropagation()}>
{t('notebookSuggestion.moveToNotebook')}
handleMoveToNotebook(null)}> {t('notebookSuggestion.generalNotes')} {notebooks.map((notebook: any) => ( handleMoveToNotebook(notebook.id)} > {notebook.name} ))}
{/* Dismiss Button */} {/* Delete Button */}

{optimisticNote.title || t('notes.untitled')}

{(optimisticNote.content && typeof optimisticNote.content === 'string') ? optimisticNote.content.substring(0, 80) : ''} {optimisticNote.content && typeof optimisticNote.content === 'string' && optimisticNote.content.length > 80 && '...'}

{timeAgo}
{optimisticNote.isPinned && ( )} {optimisticNote.notebookId && (
)} {optimisticNote.labels && optimisticNote.labels.length > 0 && (
)}
) } function getCompactTime(date: Date | string, t: (key: string, params?: Record) => string): string { const now = new Date() const then = date instanceof Date ? date : new Date(date) if (isNaN(then.getTime())) { console.warn('Invalid date provided to getCompactTime:', date) return t('common.error') } const seconds = Math.floor((now.getTime() - then.getTime()) / 1000) const minutes = Math.floor(seconds / 60) const hours = Math.floor(minutes / 60) if (seconds < 60) return t('time.justNow') if (minutes < 60) return t('time.minutesAgo', { count: minutes }) if (hours < 24) return t('time.hoursAgo', { count: hours }) const days = Math.floor(hours / 24) return t('time.daysAgo', { count: days }) }