import { useEffect, useState } from 'react' import { useNavigate, useLocation } from 'react-router-dom' import { LayoutGrid, Box, PenLine, Clock, FileText, Bookmark, BarChart3, Settings, PanelLeftClose, PanelLeftOpen, MessageSquareText } from 'lucide-react' import { cn } from '@/lib/utils' import { useUserPreferencesStore } from '@/store/userPreferencesStore' import { CategoryList } from '@/components/sidebar/CategoryList' import { TagCloud } from '@/components/sidebar/TagCloud' import { PinnedFlowsSection } from '@/components/sidebar/PinnedFlowsSection' import { NavItem } from './NavItem' import { categoriesApi, tagsApi, sessionsApi, treesApi } from '@/api' import { pinnedFlowsApi } from '@/api/pinnedFlows' import type { PinnedFlow } from '@/api/pinnedFlows' import { toast } from '@/lib/toast' interface CategoryItem { id: string name: string color: string count: number } export function Sidebar() { const sidebarCollapsed = useUserPreferencesStore(s => s.sidebarCollapsed) const toggleSidebar = useUserPreferencesStore(s => s.toggleSidebar) const [categories, setCategories] = useState([]) const [tags, setTags] = useState([]) const [activeCategoryId, setActiveCategoryId] = useState(null) const [activeTags, setActiveTags] = useState([]) const [activeSessionCount, setActiveSessionCount] = useState(0) const [pinnedFlows, setPinnedFlows] = useState([]) const [treeCounts, setTreeCounts] = useState({ total: 0, troubleshooting: 0, procedural: 0, maintenance: 0 }) // Fetch sidebar data on mount useEffect(() => { const fetchData = async () => { try { const [cats, tagList, activeSessions, allTrees, pinnedData] = await Promise.all([ categoriesApi.list(), tagsApi.list().catch(() => []), sessionsApi.list({ completed: false, size: 50 }).catch(() => []), treesApi.list({ sort_by: 'name' }).catch(() => []), pinnedFlowsApi.list().catch(() => ({ items: [], count: 0 })), ]) setCategories(cats.map(c => ({ id: c.id, name: c.name, color: c.color || '#3b82f6', count: c.tree_count || 0, }))) setTags(tagList.map((t: { name: string }) => t.name).slice(0, 15)) setActiveSessionCount(activeSessions.length) setPinnedFlows(pinnedData.items) const total = allTrees.length const troubleshooting = allTrees.filter(t => t.tree_type === 'troubleshooting').length const procedural = allTrees.filter(t => t.tree_type === 'procedural').length const maintenance = allTrees.filter(t => t.tree_type === 'maintenance').length setTreeCounts({ total, troubleshooting, procedural, maintenance }) } catch { // Silently handle errors } } fetchData() }, []) const navigate = useNavigate() const location = useLocation() // Sync active filters from URL when on /trees page useEffect(() => { if (location.pathname === '/trees') { const params = new URLSearchParams(location.search) setActiveCategoryId(params.get('category') || null) const tagsParam = params.get('tags') setActiveTags(tagsParam ? tagsParam.split(',') : []) } }, [location.pathname, location.search]) const handleCategorySelect = (id: string | null) => { setActiveCategoryId(id) const params = new URLSearchParams(location.search) if (id) { params.set('category', id) } else { params.delete('category') } navigate(`/trees?${params.toString()}`) } const handleTagClick = (tag: string) => { const next = activeTags.includes(tag) ? activeTags.filter(t => t !== tag) : [...activeTags, tag] setActiveTags(next) const params = new URLSearchParams(location.search) if (next.length > 0) { params.set('tags', next.join(',')) } else { params.delete('tags') } navigate(`/trees?${params.toString()}`) } const handleUnpin = async (treeId: string) => { try { await pinnedFlowsApi.unpin(treeId) setPinnedFlows(prev => prev.filter(f => f.tree_id !== treeId)) toast.success('Unpinned from sidebar') } catch { toast.error('Failed to unpin flow') } } const handleSidebarWheel = (e: React.WheelEvent) => { const sidebar = e.currentTarget const canSidebarScroll = sidebar.scrollHeight > sidebar.clientHeight const atTop = sidebar.scrollTop <= 0 const atBottom = sidebar.scrollTop + sidebar.clientHeight >= sidebar.scrollHeight - 1 // If sidebar can't consume wheel movement, forward it to main content scroller. if (!canSidebarScroll || (e.deltaY < 0 && atTop) || (e.deltaY > 0 && atBottom)) { const main = document.querySelector('.main-content') as HTMLElement | null if (main) { main.scrollTop += e.deltaY e.preventDefault() } } } return ( ) }