feat: restructure sidebar with stats bar, activity feed, and grouped nav
Dashboard-first layout with Resolve/Build/Insights groups. AI split: FlowPilot (Resolve) + Flow Assist (Build). Stats bar: Resolved/Active/In Session daily counters. Activity feed: active sessions with CW ticket #, recent completions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,17 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { LayoutGrid, Network, Wrench, Clock, FileOutput, BarChart3, Settings, PanelLeftClose, PanelLeftOpen, MessageSquareText, BotMessageSquare, BookOpen, Lightbulb, Code2, Library } from 'lucide-react'
|
import { useLocation } from 'react-router-dom'
|
||||||
|
import {
|
||||||
|
LayoutGrid, Network, Wrench, Clock, FileOutput, BarChart3,
|
||||||
|
Settings, PanelLeftClose, PanelLeftOpen, MessageSquareText,
|
||||||
|
BookOpen, Lightbulb, Code2, Library, Brain, WandSparkles,
|
||||||
|
} from 'lucide-react'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
|
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
|
||||||
import { usePinnedFlowsStore } from '@/store/pinnedFlowsStore'
|
import { sidebarApi } from '@/api'
|
||||||
import { PinnedFlowsSection } from '@/components/sidebar/PinnedFlowsSection'
|
import type { SidebarStatsResponse } from '@/api/sidebar'
|
||||||
|
import { SidebarStatsBar } from '@/components/sidebar/SidebarStatsBar'
|
||||||
|
import { SidebarActivityFeed } from '@/components/sidebar/SidebarActivityFeed'
|
||||||
import { NavItem } from './NavItem'
|
import { NavItem } from './NavItem'
|
||||||
import { sessionsApi, treesApi } from '@/api'
|
|
||||||
|
|
||||||
// Semantic icon colors — each nav item gets a unique color for visual landmarks
|
// Semantic icon colors — each nav item gets a unique color for visual landmarks
|
||||||
const NAV_COLORS = {
|
const NAV_COLORS = {
|
||||||
@@ -14,7 +20,8 @@ const NAV_COLORS = {
|
|||||||
editor: '#f59e0b', // amber-500
|
editor: '#f59e0b', // amber-500
|
||||||
sessions: '#34d399', // emerald-400
|
sessions: '#34d399', // emerald-400
|
||||||
exports: '#60a5fa', // blue-400
|
exports: '#60a5fa', // blue-400
|
||||||
ai: '#e879f9', // fuchsia-400
|
flowPilot: '#e879f9', // fuchsia-400
|
||||||
|
flowAssist:'#f472b6', // pink-400
|
||||||
stepLib: '#fb923c', // orange-400
|
stepLib: '#fb923c', // orange-400
|
||||||
scripts: '#2dd4bf', // teal-400
|
scripts: '#2dd4bf', // teal-400
|
||||||
kb: '#fb7185', // rose-400
|
kb: '#fb7185', // rose-400
|
||||||
@@ -26,40 +33,14 @@ const NAV_COLORS = {
|
|||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
const sidebarCollapsed = useUserPreferencesStore(s => s.sidebarCollapsed)
|
const sidebarCollapsed = useUserPreferencesStore(s => s.sidebarCollapsed)
|
||||||
const toggleSidebar = useUserPreferencesStore(s => s.toggleSidebar)
|
const toggleSidebar = useUserPreferencesStore(s => s.toggleSidebar)
|
||||||
|
const location = useLocation()
|
||||||
|
|
||||||
const pinnedItems = usePinnedFlowsStore((s) => s.items)
|
const [stats, setStats] = useState<SidebarStatsResponse | null>(null)
|
||||||
const loadPinned = usePinnedFlowsStore((s) => s.load)
|
|
||||||
const unpinFlow = usePinnedFlowsStore((s) => s.unpin)
|
|
||||||
|
|
||||||
const [activeSessionCount, setActiveSessionCount] = useState(0)
|
// Fetch sidebar stats — refreshes on navigation
|
||||||
const [treeCounts, setTreeCounts] = useState({ total: 0, troubleshooting: 0, procedural: 0, maintenance: 0 })
|
|
||||||
|
|
||||||
// Load pinned flows on mount
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadPinned()
|
sidebarApi.getStats().then(setStats).catch(() => {})
|
||||||
}, [loadPinned])
|
}, [location.pathname])
|
||||||
|
|
||||||
// Fetch sidebar data on mount
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchData = async () => {
|
|
||||||
try {
|
|
||||||
const [activeSessions, allTrees] = await Promise.all([
|
|
||||||
sessionsApi.list({ completed: false, size: 50 }).catch(() => []),
|
|
||||||
treesApi.list({ sort_by: 'name' }).catch(() => []),
|
|
||||||
])
|
|
||||||
setActiveSessionCount(activeSessions.length)
|
|
||||||
|
|
||||||
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 handleSidebarWheel = (e: React.WheelEvent<HTMLElement>) => {
|
const handleSidebarWheel = (e: React.WheelEvent<HTMLElement>) => {
|
||||||
const sidebar = e.currentTarget
|
const sidebar = e.currentTarget
|
||||||
@@ -93,14 +74,15 @@ export function Sidebar() {
|
|||||||
{/* Collapsed: icon-only nav */}
|
{/* Collapsed: icon-only nav */}
|
||||||
<div className="flex flex-col items-center px-1.5 py-3 space-y-1">
|
<div className="flex flex-col items-center px-1.5 py-3 space-y-1">
|
||||||
<NavItem href="/" icon={LayoutGrid} label="Dashboard" iconColor={NAV_COLORS.dashboard} collapsed />
|
<NavItem href="/" icon={LayoutGrid} label="Dashboard" iconColor={NAV_COLORS.dashboard} collapsed />
|
||||||
|
<NavItem href="/sessions" icon={Clock} label="Sessions" badge={stats?.active_count || undefined} iconColor={NAV_COLORS.sessions} collapsed />
|
||||||
<NavItem href="/trees" icon={Network} label="All Flows" matchPaths={['/trees', '/flows']} iconColor={NAV_COLORS.flows} collapsed />
|
<NavItem href="/trees" icon={Network} label="All Flows" matchPaths={['/trees', '/flows']} iconColor={NAV_COLORS.flows} collapsed />
|
||||||
<NavItem href="/my-trees" icon={Wrench} label="Flow Editor" iconColor={NAV_COLORS.editor} collapsed />
|
<NavItem href="/assistant" icon={Brain} label="FlowPilot" iconColor={NAV_COLORS.flowPilot} collapsed />
|
||||||
<NavItem href="/sessions" icon={Clock} label="Sessions" badge={activeSessionCount || undefined} iconColor={NAV_COLORS.sessions} collapsed />
|
|
||||||
<NavItem href="/shares" icon={FileOutput} label="Exports" iconColor={NAV_COLORS.exports} collapsed />
|
|
||||||
<NavItem href="/assistant" icon={BotMessageSquare} label="AI Assistant" iconColor={NAV_COLORS.ai} collapsed />
|
|
||||||
<NavItem href="/step-library" icon={Library} label="Step Library" iconColor={NAV_COLORS.stepLib} collapsed />
|
|
||||||
<NavItem href="/scripts" icon={Code2} label="Script Library" iconColor={NAV_COLORS.scripts} collapsed />
|
<NavItem href="/scripts" icon={Code2} label="Script Library" iconColor={NAV_COLORS.scripts} collapsed />
|
||||||
|
<NavItem href="/my-trees" icon={Wrench} label="Flow Editor" iconColor={NAV_COLORS.editor} collapsed />
|
||||||
|
<NavItem href="/flow-assist" icon={WandSparkles} label="Flow Assist" iconColor={NAV_COLORS.flowAssist} collapsed />
|
||||||
|
<NavItem href="/step-library" icon={Library} label="Step Library" iconColor={NAV_COLORS.stepLib} collapsed />
|
||||||
<NavItem href="/kb-accelerator" icon={Lightbulb} label="KB Accelerator" iconColor={NAV_COLORS.kb} collapsed />
|
<NavItem href="/kb-accelerator" icon={Lightbulb} label="KB Accelerator" iconColor={NAV_COLORS.kb} collapsed />
|
||||||
|
<NavItem href="/shares" icon={FileOutput} label="Exports" iconColor={NAV_COLORS.exports} collapsed />
|
||||||
<NavItem href="/analytics" icon={BarChart3} label="Analytics" iconColor={NAV_COLORS.analytics} collapsed />
|
<NavItem href="/analytics" icon={BarChart3} label="Analytics" iconColor={NAV_COLORS.analytics} collapsed />
|
||||||
<NavItem href="/guides" icon={BookOpen} label="User Guides" iconColor={NAV_COLORS.guides} collapsed />
|
<NavItem href="/guides" icon={BookOpen} label="User Guides" iconColor={NAV_COLORS.guides} collapsed />
|
||||||
<NavItem href="/feedback" icon={MessageSquareText} label="Feedback" iconColor={NAV_COLORS.feedback} collapsed />
|
<NavItem href="/feedback" icon={MessageSquareText} label="Feedback" iconColor={NAV_COLORS.feedback} collapsed />
|
||||||
@@ -108,34 +90,62 @@ export function Sidebar() {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Pinned Flows */}
|
{/* Stats Bar */}
|
||||||
<PinnedFlowsSection flows={pinnedItems} onUnpin={unpinFlow} />
|
<SidebarStatsBar
|
||||||
|
resolved={stats?.resolved_today ?? 0}
|
||||||
|
active={stats?.active_count ?? 0}
|
||||||
|
sessionMinutes={stats?.total_session_minutes_today ?? 0}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Activity Feed */}
|
||||||
|
<SidebarActivityFeed
|
||||||
|
activeSessions={stats?.active_sessions ?? []}
|
||||||
|
recentCompletions={stats?.recent_completions ?? []}
|
||||||
|
totalActive={stats?.active_count ?? 0}
|
||||||
|
/>
|
||||||
|
|
||||||
<div style={{ borderBottom: '1px solid var(--glass-border)' }} />
|
<div style={{ borderBottom: '1px solid var(--glass-border)' }} />
|
||||||
|
|
||||||
{/* Primary Navigation */}
|
{/* Navigation */}
|
||||||
<div className="px-3 py-2 space-y-0.5">
|
<div className="px-3 py-2 space-y-0.5">
|
||||||
|
{/* Dashboard (standalone) */}
|
||||||
<NavItem href="/" icon={LayoutGrid} label="Dashboard" iconColor={NAV_COLORS.dashboard} />
|
<NavItem href="/" icon={LayoutGrid} label="Dashboard" iconColor={NAV_COLORS.dashboard} />
|
||||||
|
|
||||||
|
{/* Resolve */}
|
||||||
|
<div className="font-label text-[0.5625rem] uppercase tracking-[0.12em] text-[#5a6170] px-3 pt-3 pb-1">
|
||||||
|
Resolve
|
||||||
|
</div>
|
||||||
|
<NavItem href="/sessions" icon={Clock} label="Sessions" badge={stats?.active_count || undefined} iconColor={NAV_COLORS.sessions} />
|
||||||
<NavItem
|
<NavItem
|
||||||
href="/trees"
|
href="/trees"
|
||||||
icon={Network}
|
icon={Network}
|
||||||
label="All Flows"
|
label="All Flows"
|
||||||
badge={treeCounts.total || undefined}
|
badge={stats?.tree_counts.total || undefined}
|
||||||
iconColor={NAV_COLORS.flows}
|
iconColor={NAV_COLORS.flows}
|
||||||
matchPaths={['/trees', '/flows']}
|
matchPaths={['/trees', '/flows']}
|
||||||
children={[
|
children={[
|
||||||
{ href: '/trees?type=troubleshooting', label: 'Troubleshooting', count: treeCounts.troubleshooting || undefined },
|
{ href: '/trees?type=troubleshooting', label: 'Troubleshooting', count: stats?.tree_counts.troubleshooting || undefined },
|
||||||
{ href: '/trees?type=procedural', label: 'Projects', count: treeCounts.procedural || undefined },
|
{ href: '/trees?type=procedural', label: 'Projects', count: stats?.tree_counts.procedural || undefined },
|
||||||
{ href: '/trees?type=maintenance', label: 'Maintenance', count: treeCounts.maintenance || undefined },
|
{ href: '/trees?type=maintenance', label: 'Maintenance', count: stats?.tree_counts.maintenance || undefined },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<NavItem href="/my-trees" icon={Wrench} label="Flow Editor" iconColor={NAV_COLORS.editor} />
|
<NavItem href="/assistant" icon={Brain} label="FlowPilot" iconColor={NAV_COLORS.flowPilot} />
|
||||||
<NavItem href="/sessions" icon={Clock} label="Sessions" badge={activeSessionCount || undefined} iconColor={NAV_COLORS.sessions} />
|
|
||||||
<NavItem href="/shares" icon={FileOutput} label="Exports" iconColor={NAV_COLORS.exports} />
|
|
||||||
<NavItem href="/assistant" icon={BotMessageSquare} label="AI Assistant" iconColor={NAV_COLORS.ai} />
|
|
||||||
<NavItem href="/step-library" icon={Library} label="Step Library" iconColor={NAV_COLORS.stepLib} />
|
|
||||||
<NavItem href="/scripts" icon={Code2} label="Script Library" iconColor={NAV_COLORS.scripts} />
|
<NavItem href="/scripts" icon={Code2} label="Script Library" iconColor={NAV_COLORS.scripts} />
|
||||||
|
|
||||||
|
{/* Build */}
|
||||||
|
<div className="font-label text-[0.5625rem] uppercase tracking-[0.12em] text-[#5a6170] px-3 pt-3 pb-1">
|
||||||
|
Build
|
||||||
|
</div>
|
||||||
|
<NavItem href="/my-trees" icon={Wrench} label="Flow Editor" iconColor={NAV_COLORS.editor} />
|
||||||
|
<NavItem href="/flow-assist" icon={WandSparkles} label="Flow Assist" iconColor={NAV_COLORS.flowAssist} />
|
||||||
|
<NavItem href="/step-library" icon={Library} label="Step Library" iconColor={NAV_COLORS.stepLib} />
|
||||||
<NavItem href="/kb-accelerator" icon={Lightbulb} label="KB Accelerator" iconColor={NAV_COLORS.kb} />
|
<NavItem href="/kb-accelerator" icon={Lightbulb} label="KB Accelerator" iconColor={NAV_COLORS.kb} />
|
||||||
|
|
||||||
|
{/* Insights */}
|
||||||
|
<div className="font-label text-[0.5625rem] uppercase tracking-[0.12em] text-[#5a6170] px-3 pt-3 pb-1">
|
||||||
|
Insights
|
||||||
|
</div>
|
||||||
|
<NavItem href="/shares" icon={FileOutput} label="Exports" iconColor={NAV_COLORS.exports} />
|
||||||
<NavItem href="/analytics" icon={BarChart3} label="Analytics" iconColor={NAV_COLORS.analytics} />
|
<NavItem href="/analytics" icon={BarChart3} label="Analytics" iconColor={NAV_COLORS.analytics} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user