import React from 'react'; import { Note } from '../types'; import { categoryColorsSync } from '../services/categoryColorsSync'; import { SyncStatus } from '../services/syncManager'; interface NotesListProps { notes: Note[]; selectedNoteId: number | null; onSelectNote: (id: number) => void; onCreateNote: () => void; onDeleteNote: (note: Note) => void; onSync: () => void; searchText: string; onSearchChange: (text: string) => void; showFavoritesOnly: boolean; onToggleFavorites: () => void; hasUnsavedChanges: boolean; syncStatus: SyncStatus; pendingSyncCount: number; isOnline: boolean; } export function NotesList({ notes, selectedNoteId, onSelectNote, onCreateNote, onDeleteNote, onSync, searchText, onSearchChange, showFavoritesOnly, onToggleFavorites, hasUnsavedChanges, syncStatus: _syncStatus, pendingSyncCount, isOnline, }: NotesListProps) { const [isSyncing, setIsSyncing] = React.useState(false); const [deleteClickedId, setDeleteClickedId] = React.useState(null); const [width, setWidth] = React.useState(() => { const saved = localStorage.getItem('notesListWidth'); return saved ? parseInt(saved, 10) : 320; }); const [isResizing, setIsResizing] = React.useState(false); const [, forceUpdate] = React.useReducer(x => x + 1, 0); const containerRef = React.useRef(null); // Listen for category color changes React.useEffect(() => { const handleCustomEvent = () => forceUpdate(); window.addEventListener('categoryColorChanged', handleCustomEvent); return () => { window.removeEventListener('categoryColorChanged', handleCustomEvent); }; }, []); const handleSync = async () => { setIsSyncing(true); await onSync(); setTimeout(() => setIsSyncing(false), 500); }; React.useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (!isResizing) return; const newWidth = e.clientX - (containerRef.current?.getBoundingClientRect().left || 0); if (newWidth >= 240 && newWidth <= 600) { setWidth(newWidth); localStorage.setItem('notesListWidth', newWidth.toString()); } }; const handleMouseUp = () => { setIsResizing(false); }; if (isResizing) { document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); document.body.style.cursor = 'ew-resize'; document.body.style.userSelect = 'none'; } return () => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); document.body.style.cursor = ''; document.body.style.userSelect = ''; }; }, [isResizing]); const handleDeleteClick = (note: Note, e: React.MouseEvent) => { e.stopPropagation(); // Prevent deletion if there are unsaved changes on a different note if (hasUnsavedChanges && note.id !== selectedNoteId) { return; } if (deleteClickedId === note.id) { // Second click - actually delete onDeleteNote(note); setDeleteClickedId(null); } else { // First click - show confirmation state setDeleteClickedId(note.id); // Reset after 3 seconds setTimeout(() => setDeleteClickedId(null), 3000); } }; const formatDate = (timestamp: number) => { const date = new Date(timestamp * 1000); const now = new Date(); const diff = now.getTime() - date.getTime(); const minutes = Math.floor(diff / 60000); const hours = Math.floor(diff / 3600000); const days = Math.floor(diff / 86400000); if (minutes < 1) return 'just now'; if (minutes < 60) return `${minutes}m ago`; if (hours < 24) return `${hours}h ago`; if (days < 7) return `${days}d ago`; return date.toLocaleDateString(); }; const getPreview = (content: string) => { // grab first 100 characters of note's content, remove markdown syntax from the preview const previewContent = content.substring(0, 100); const cleanedPreview = previewContent.replace(/[#*`]/g, ''); return cleanedPreview; }; const getCategoryColor = (category: string) => { // Color palette matching CategoriesSidebar const colors = [ { bg: 'bg-blue-100 dark:bg-blue-900/30', text: 'text-blue-700 dark:text-blue-300' }, { bg: 'bg-green-100 dark:bg-green-900/30', text: 'text-green-700 dark:text-green-300' }, { bg: 'bg-purple-100 dark:bg-purple-900/30', text: 'text-purple-700 dark:text-purple-300' }, { bg: 'bg-pink-100 dark:bg-pink-900/30', text: 'text-pink-700 dark:text-pink-300' }, { bg: 'bg-yellow-100 dark:bg-yellow-900/30', text: 'text-yellow-700 dark:text-yellow-300' }, { bg: 'bg-red-100 dark:bg-red-900/30', text: 'text-red-700 dark:text-red-300' }, { bg: 'bg-orange-100 dark:bg-orange-900/30', text: 'text-orange-700 dark:text-orange-300' }, { bg: 'bg-teal-100 dark:bg-teal-900/30', text: 'text-teal-700 dark:text-teal-300' }, { bg: 'bg-indigo-100 dark:bg-indigo-900/30', text: 'text-indigo-700 dark:text-indigo-300' }, { bg: 'bg-cyan-100 dark:bg-cyan-900/30', text: 'text-cyan-700 dark:text-cyan-300' }, ]; // Only return color if explicitly set by user const colorIndex = categoryColorsSync.getColor(category); if (colorIndex !== undefined) { return colors[colorIndex]; } // No color set - return null to indicate no badge should be shown return null; }; return (

Notes

{!isOnline && ( Offline )} {pendingSyncCount > 0 && ( {pendingSyncCount} pending )}
onSearchChange(e.target.value)} placeholder="Search notes..." className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
{notes.length} notes
{notes.length === 0 ? (

No notes found

) : ( notes.map((note) => (
{ // Prevent switching if current note has unsaved changes if (hasUnsavedChanges && note.id !== selectedNoteId) { return; } onSelectNote(note.id); }} className={`p-3 border-b border-gray-200 dark:border-gray-700 transition-colors group ${ note.id === selectedNoteId ? 'bg-blue-50 dark:bg-gray-800 border-l-4 border-l-blue-500' : hasUnsavedChanges ? 'cursor-not-allowed opacity-50' : 'cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800' }`} title={hasUnsavedChanges && note.id !== selectedNoteId ? 'Save current note before switching' : ''} >
{note.favorite && ( )}

{note.title || 'Untitled'}

{deleteClickedId === note.id && ( Click again to delete )}
{formatDate(note.modified)} {note.category && (() => { const colors = getCategoryColor(note.category); if (!colors) return null; return ( {note.category} ); })()}
{getPreview(note.content) && (

{getPreview(note.content)}

)}
)) )}
{/* Resize Handle */}
{ e.preventDefault(); setIsResizing(true); }} >
); }