import React, { useState, useMemo } from "react"; import { motion } from "framer-motion"; import { FolderOpen, Calendar, FileText, ChevronRight, Settings, MoreVertical, Search, X, Plus } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { useTranslation } from "@/hooks/useTranslation"; import type { Project } from "@/lib/api"; import { cn } from "@/lib/utils"; import { formatTimeAgo } from "@/lib/date-utils"; import { Pagination } from "@/components/ui/pagination"; interface ProjectListProps { /** * Array of projects to display */ projects: Project[]; /** * Callback when a project is clicked */ onProjectClick: (project: Project) => void; /** * Callback when hooks configuration is clicked */ onProjectSettings?: (project: Project) => void; /** * Callback when new session button is clicked */ onNewSession?: () => void; /** * Whether the list is currently loading */ loading?: boolean; /** * Optional className for styling */ className?: string; } const ITEMS_PER_PAGE = 12; /** * Extracts the project name from the full path */ const getProjectName = (path: string): string => { const parts = path.split('/').filter(Boolean); return parts[parts.length - 1] || path; }; /** * ProjectList component - Displays a paginated list of projects with hover animations * * @example * console.log('Selected:', project)} * /> */ export const ProjectList: React.FC = ({ projects, onProjectClick, onProjectSettings, onNewSession, className, }) => { const { t } = useTranslation(); const [currentPage, setCurrentPage] = useState(1); const [searchQuery, setSearchQuery] = useState(""); // Sort and filter projects const filteredAndSortedProjects = useMemo(() => { // First, sort by last_session_time in descending order (newest first) let sorted = [...projects].sort((a, b) => b.last_session_time - a.last_session_time); // Then filter by search query if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); sorted = sorted.filter(project => getProjectName(project.path).toLowerCase().includes(query) ); } return sorted; }, [projects, searchQuery]); // Calculate pagination const totalPages = Math.ceil(filteredAndSortedProjects.length / ITEMS_PER_PAGE); const startIndex = (currentPage - 1) * ITEMS_PER_PAGE; const endIndex = startIndex + ITEMS_PER_PAGE; const currentProjects = filteredAndSortedProjects.slice(startIndex, endIndex); // Reset to page 1 if projects or search query changes React.useEffect(() => { setCurrentPage(1); }, [projects.length, searchQuery]); return (
{/* Action bar with new session button and search */}
{/* New session button */} {onNewSession && ( )} {/* Search and results info */}
setSearchQuery(e.target.value)} className="pl-9 pr-9 h-10" /> {searchQuery && ( )}
{/* Results info */}
{searchQuery ? ( {t('showingResults')}: {filteredAndSortedProjects.length} / {projects.length} ) : ( {t('totalProjects')}: {projects.length} )}
{/* Empty state */} {filteredAndSortedProjects.length === 0 && searchQuery && (

{t('noSearchResults')}

{t('noProjectsMatchSearch')} "{searchQuery}"

)} {/* Project grid */}
{currentProjects.map((project, index) => ( onProjectClick(project)} > {/* Hover gradient effect */}

{getProjectName(project.path)}

{project.sessions.length > 0 && ( {project.sessions.length} )}

{project.path}

{formatTimeAgo(project.created_at * 1000)}
{project.sessions.length}
{onProjectSettings && ( e.stopPropagation()}> { e.stopPropagation(); onProjectSettings(project); }} > {t('settings.hooks')} )}
))}
); };