- 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
109 lines
3.5 KiB
TypeScript
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)}
|
|
/>
|
|
)}
|
|
</>
|
|
)
|
|
}
|