Keep/keep-notes/components/note-grid.tsx
sepehr 8d95f34fcc fix: Add debounced Undo/Redo system to avoid character-by-character history
- Add debounced state updates for title and content (500ms delay)
- Immediate UI updates with delayed history saving
- Prevent one-letter-per-undo issue
- Add cleanup for debounce timers on unmount
2026-01-04 14:28:11 +01:00

109 lines
3.5 KiB
TypeScript

'use client'
import { Note } from '@/lib/types'
import { NoteCard } from './note-card'
import { useState } from 'react'
import { NoteEditor } from './note-editor'
import { reorderNotes } from '@/app/actions/notes'
interface NoteGridProps {
notes: Note[]
}
export function NoteGrid({ notes }: NoteGridProps) {
const [editingNote, setEditingNote] = useState<Note | null>(null)
const [draggedNote, setDraggedNote] = useState<Note | null>(null)
const [dragOverNote, setDragOverNote] = useState<Note | null>(null)
const pinnedNotes = notes.filter(note => note.isPinned).sort((a, b) => a.order - b.order)
const unpinnedNotes = notes.filter(note => !note.isPinned).sort((a, b) => a.order - b.order)
const handleDragStart = (note: Note) => {
setDraggedNote(note)
}
const handleDragEnd = async () => {
if (draggedNote && dragOverNote && draggedNote.id !== dragOverNote.id) {
// Reorder notes
const sourceIndex = notes.findIndex(n => n.id === draggedNote.id)
const targetIndex = notes.findIndex(n => n.id === dragOverNote.id)
await reorderNotes(draggedNote.id, dragOverNote.id)
}
setDraggedNote(null)
setDragOverNote(null)
}
const handleDragOver = (note: Note) => {
if (draggedNote && draggedNote.id !== note.id) {
setDragOverNote(note)
}
}
return (
<>
<div className="space-y-8">
{pinnedNotes.length > 0 && (
<div>
<h2 className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-3 px-2">
Pinned
</h2>
<div className="columns-1 sm:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5 gap-4 space-y-4">
{pinnedNotes.map(note => (
<div key={note.id} className="break-inside-avoid mb-4">
<NoteCard
note={note}
onEdit={setEditingNote}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
isDragging={draggedNote?.id === note.id}
/>
</div>
))}
</div>
</div>
)}
{unpinnedNotes.length > 0 && (
<div>
{pinnedNotes.length > 0 && (
<h2 className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-3 px-2">
Others
</h2>
)}
<div className="columns-1 sm:columns-2 lg:columns-3 xl:columns-4 2xl:columns-5 gap-4 space-y-4">
{unpinnedNotes.map(note => (
<div key={note.id} className="break-inside-avoid mb-4">
<NoteCard
note={note}
onEdit={setEditingNote}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
isDragging={draggedNote?.id === note.id}
/>
</div>
))}
</div>
</div>
)}
{notes.length === 0 && (
<div className="text-center py-16">
<p className="text-gray-500 dark:text-gray-400 text-lg">No notes yet</p>
<p className="text-gray-400 dark:text-gray-500 text-sm mt-2">Create your first note to get started</p>
</div>
)}
</div>
{editingNote && (
<NoteEditor
note={editingNote}
onClose={() => setEditingNote(null)}
/>
)}
</>
)
}