import { useEffect, useState, useCallback, useMemo, useRef } from 'react' import { useSessionStore } from '../../stores/sessionStore' import { useUIStore } from '../../stores/uiStore' import { useTranslation } from '../../i18n' import { ProjectFilter } from './ProjectFilter' import type { SessionListItem } from '../../types/session' import { useTabStore, SETTINGS_TAB_ID, SCHEDULED_TAB_ID } from '../../stores/tabStore' import { useChatStore } from '../../stores/chatStore' const isTauri = typeof window !== 'undefined' && ('__TAURI_INTERNALS__' in window || '__TAURI__' in window) const isWindows = typeof navigator !== 'undefined' && /Win/.test(navigator.platform) type TimeGroup = 'today' | 'yesterday' | 'last7days' | 'last30days' | 'older' const TIME_GROUP_ORDER: TimeGroup[] = ['today', 'yesterday', 'last7days', 'last30days', 'older'] export function Sidebar() { const sessions = useSessionStore((s) => s.sessions) const selectedProjects = useSessionStore((s) => s.selectedProjects) const error = useSessionStore((s) => s.error) const fetchSessions = useSessionStore((s) => s.fetchSessions) const deleteSession = useSessionStore((s) => s.deleteSession) const renameSession = useSessionStore((s) => s.renameSession) const addToast = useUIStore((s) => s.addToast) const sidebarOpen = useUIStore((s) => s.sidebarOpen) const toggleSidebar = useUIStore((s) => s.toggleSidebar) const activeTabId = useTabStore((s) => s.activeTabId) const closeTab = useTabStore((s) => s.closeTab) const disconnectSession = useChatStore((s) => s.disconnectSession) const [searchQuery, setSearchQuery] = useState('') const [contextMenu, setContextMenu] = useState<{ id: string; x: number; y: number } | null>(null) const [renamingId, setRenamingId] = useState(null) const [renameValue, setRenameValue] = useState('') useEffect(() => { fetchSessions() }, [fetchSessions]) useEffect(() => { if (!contextMenu || sidebarOpen) return setContextMenu(null) }, [contextMenu, sidebarOpen]) useEffect(() => { if (!contextMenu) return const close = () => setContextMenu(null) document.addEventListener('click', close) return () => document.removeEventListener('click', close) }, [contextMenu]) const filteredSessions = useMemo(() => { let result = sessions if (selectedProjects.length > 0) { result = result.filter((s) => selectedProjects.includes(s.projectPath)) } if (searchQuery) { const q = searchQuery.toLowerCase() result = result.filter((s) => s.title.toLowerCase().includes(q)) } return result }, [sessions, selectedProjects, searchQuery]) const timeGroups = useMemo(() => groupByTime(filteredSessions), [filteredSessions]) const handleContextMenu = useCallback((e: React.MouseEvent, id: string) => { e.preventDefault() setContextMenu({ id, x: e.clientX, y: e.clientY }) }, []) const handleDelete = useCallback(async (id: string) => { setContextMenu(null) await deleteSession(id) disconnectSession(id) closeTab(id) }, [closeTab, deleteSession, disconnectSession]) const handleStartRename = useCallback((id: string, currentTitle: string) => { setContextMenu(null) setRenamingId(id) setRenameValue(currentTitle) }, []) const handleFinishRename = useCallback(async () => { if (renamingId && renameValue.trim()) { await renameSession(renamingId, renameValue.trim()) } setRenamingId(null) setRenameValue('') }, [renamingId, renameValue, renameSession]) const startDraggingRef = useRef<(() => Promise) | null>(null) useEffect(() => { if (!isTauri) return import(/* @vite-ignore */ '@tauri-apps/api/window') .then(({ getCurrentWindow }) => { const win = getCurrentWindow() startDraggingRef.current = () => win.startDragging() }) .catch(() => {}) }, []) const handleSidebarDrag = useCallback((e: React.MouseEvent) => { if ((e.target as HTMLElement).closest('button, input, textarea, select, a, [role="button"]')) return startDraggingRef.current?.() }, []) const t = useTranslation() const timeGroupLabels: Record = { today: t('sidebar.timeGroup.today'), yesterday: t('sidebar.timeGroup.yesterday'), last7days: t('sidebar.timeGroup.last7days'), last30days: t('sidebar.timeGroup.last30days'), older: t('sidebar.timeGroup.older'), } return ( ) } function groupByTime(sessions: SessionListItem[]): Map { const groups = new Map() const now = new Date() const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime() const startOfYesterday = startOfToday - 86400000 const sevenDaysAgo = startOfToday - 7 * 86400000 const thirtyDaysAgo = startOfToday - 30 * 86400000 for (const session of sessions) { const ts = new Date(session.modifiedAt).getTime() let group: TimeGroup if (ts >= startOfToday) group = 'today' else if (ts >= startOfYesterday) group = 'yesterday' else if (ts >= sevenDaysAgo) group = 'last7days' else if (ts >= thirtyDaysAgo) group = 'last30days' else group = 'older' if (!groups.has(group)) groups.set(group, []) groups.get(group)!.push(session) } return groups } function NavItem({ active, collapsed, label, onClick, icon, children, }: { active: boolean collapsed: boolean label: string onClick: () => void icon: React.ReactNode children: React.ReactNode }) { return ( ) } function formatRelativeTime(dateStr: string): string { const diff = Date.now() - new Date(dateStr).getTime() const min = Math.floor(diff / 60000) if (min < 1) return 'now' if (min < 60) return `${min}m` const hr = Math.floor(min / 60) if (hr < 24) return `${hr}h` const day = Math.floor(hr / 24) if (day < 30) return `${day}d` return `${Math.floor(day / 30)}mo` } function GitHubIcon() { return ( ) } function PlusIcon() { return ( ) } function ClockIcon() { return ( ) } function SearchIcon() { return ( ) } function SidebarToggleIcon({ collapsed }: { collapsed: boolean }) { return ( ) }