import { useState, useRef } from "react"; import { useNotes, useSearchNotes, type Note } from "@/hooks/use-notes"; import { CreateNoteDialog } from "@/components/create-note-dialog"; import { NoteCard } from "@/components/note-card"; import { Input } from "@/components/ui/input"; import { Search, LayoutGrid, List, Plus, Pin, X } from "lucide-react"; import { useLocation, useSearchParams, Link } from "react-router-dom"; import { Button } from "@/components/ui/button"; import clsx from "clsx"; import Masonry from "react-masonry-css"; import { NoteCardSkeletonGrid } from "@/components/note-card-skeleton"; import { Badge } from "@/components/ui/badge"; import { useKeyboardShortcuts } from "@/hooks/use-keyboard-shortcuts"; import { useTranslation } from "react-i18next"; // Masonry breakpoint columns configuration // react-masonry-css: key = max-width, value = column count at that width and below // Check order: finds first key >= viewport width const masonryBreakpoints = { default: 4, // Default for very large screens 1280: 4, // 1025-1280px: 4 columns (wide desktop) 1024: 2, // 481-1024px: 2 columns (tablets - since sidebar is overlay) 480: 1, // 0-480px: 1 column (phones) }; export default function DashboardPage() { const location = useLocation(); const [searchParams] = useSearchParams(); const isArchive = location.pathname === "/archive"; const activeTag = searchParams.get("tag"); const { t } = useTranslation(); // View mode state const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); // Search state const [searchQuery, setSearchQuery] = useState(""); const searchInputRef = useRef(null); // Create note dialog state (keyboard controlled) const [createNoteOpen, setCreateNoteOpen] = useState(false); // Keyboard shortcuts useKeyboardShortcuts({ onNewNote: () => !isArchive && setCreateNoteOpen(true), onFocusSearch: () => searchInputRef.current?.focus(), onEscape: () => { searchInputRef.current?.blur(); setCreateNoteOpen(false); }, }); // Fetch notes with optional tag filter const { data: notes, isLoading: notesLoading } = useNotes( searchQuery ? undefined : { archived: isArchive, tag: activeTag ?? undefined } ); // Fetch search results if searching const { data: searchResults, isLoading: searchLoading } = useSearchNotes(searchQuery); const displayNotes = searchQuery ? searchResults : notes; const isLoading = searchQuery ? searchLoading : notesLoading; // Separate pinned and unpinned notes const pinnedNotes = !searchQuery && !isArchive ? (displayNotes?.filter((n: Note) => n.is_pinned) ?? []) : []; const unpinnedNotes = displayNotes?.filter((n: Note) => searchQuery || isArchive || !n.is_pinned) ?? []; const renderNotes = (notesList: Note[]) => { if (viewMode === "list") { return (
{notesList.map((note: Note) => ( ))}
); } return ( {notesList.map((note: Note) => (
))}
); }; return (
{/* Action Bar */}
setSearchQuery(e.target.value)} />
{!isArchive && (
)}
{/* Active Tag Filter Badge */} {activeTag && (
Filtering by: {t(activeTag)}
)} {/* Loading State - Skeleton */} {isLoading && (
)} {/* Empty State */} {!isLoading && displayNotes?.length === 0 && (
{searchQuery ? t("No matching notes found") : activeTag ? t("No notes with tag \"${activeTag}\"") : isArchive ? t("No archived notes yet") : t("Your notes will appear here. Click + to create one.") }
)} {/* Pinned Notes Section */} {!isLoading && pinnedNotes.length > 0 && (
{t("Pinned")}
{renderNotes(pinnedNotes)}
)} {/* Other Notes Section */} {!isLoading && unpinnedNotes.length > 0 && (
{pinnedNotes.length > 0 && (
{t("Others")}
)} {renderNotes(unpinnedNotes)}
)} {/* Floating Action Button (Mobile only) */} {!isArchive && ( } /> )}
); }