import React, { useState, useRef } from "react"; import type { Project, Session } from "../types"; import { formatRelativeTime } from "../utils/timeFormat"; interface SidebarProps { projects: Project[]; sessions: Session[]; selectedProject: Project | null; selectedSession: Session | null; onSelectProject: (project: Project) => void; onSelectSession: (session: Session | null) => void; onCreateProject: () => void; onCreateSession: (projectId: string) => void; onDeleteProject: (id: string) => void; onDeleteSession: (id: string) => void; onRenameSession: (id: string, name: string) => void; loadingBySession: Record; width: number; collapsed: boolean; onCollapsedChange: (collapsed: boolean) => void; } export function Sidebar({ projects, sessions, selectedProject, selectedSession, onSelectProject, onSelectSession, onCreateProject, onCreateSession, onDeleteProject, onDeleteSession, onRenameSession, loadingBySession, width, collapsed, onCollapsedChange, }: SidebarProps) { const [renamingSessionId, setRenamingSessionId] = useState(null); const [renameValue, setRenameValue] = useState(""); // Guard against double-commit (onKeyDown Enter → unmount → onBlur) const renameCommitted = useRef(false); const startRename = (session: Session) => { renameCommitted.current = false; setRenameValue(session.name); setRenamingSessionId(session.id); }; const commitRename = (sessionId: string) => { if (renameCommitted.current) return; renameCommitted.current = true; const trimmed = renameValue.trim(); if (trimmed) onRenameSession(sessionId, trimmed); setRenamingSessionId(null); }; const cancelRename = () => { renameCommitted.current = true; setRenamingSessionId(null); }; const handleDeleteProject = (project: Project) => { if (confirm(`Delete project "${project.name}"? This cannot be undone.`)) { onDeleteProject(project.id); } }; const handleDeleteSession = (session: Session) => { if (confirm(`Delete session "${session.name}"? This cannot be undone.`)) { onDeleteSession(session.id); } }; if (collapsed) { return (
); } return (
{/* ── Header ── */}
Projects
{/* ── Tree ── */}
{projects.length === 0 && (
No projects yet
)} {projects.map((project) => { const projectSessions = sessions.filter( (s) => s.project_id === project.id ); const isSelectedProject = selectedProject?.id === project.id; return ( {/* Project row */}
{ onSelectProject(project); onSelectSession(null); }} title={project.path} > {project.name}
{/* Empty sessions hint (only under selected project) */} {projectSessions.length === 0 && isSelectedProject && (
No sessions yet
)} {/* Session rows */} {projectSessions.map((session) => { const isSelected = selectedSession?.id === session.id; const isLoading = loadingBySession[session.id] ?? false; const isRenaming = renamingSessionId === session.id; return (
{isRenaming ? ( setRenameValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") commitRename(session.id); if (e.key === "Escape") cancelRename(); }} onBlur={() => commitRename(session.id)} /> ) : ( <> onSelectSession(session)} title={`${session.name} · ${session.phase} · ${formatRelativeTime(session.updated_at)}`} > {session.name} {isLoading && ( )}
)}
); })}
); })}
); }