Files
resolutionflow/frontend/src/components/layout/Sidebar.tsx
chihlasm cf23107735 fix(escalations): add sidebar badge count + show all team escalations
- Add escalation_count to sidebar stats (team-wide requesting_escalation)
- Show badge on Escalations nav item in sidebar
- Remove user_id filter from escalation queue — show all team escalations
  including your own (needed for solo users and visibility)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 04:41:27 +00:00

173 lines
8.1 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react'
import { useLocation } from 'react-router-dom'
import {
LayoutGrid, Network, Clock, FileOutput, BarChart3, TrendingUp,
Settings, PanelLeftClose, PanelLeftOpen, MessageSquareText, ListChecks,
BookOpen, Code2, Library, AlertTriangle,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { useUserPreferencesStore } from '@/store/userPreferencesStore'
import { sidebarApi } from '@/api'
import type { SidebarStatsResponse } from '@/api/sidebar'
import { NavItem } from './NavItem'
// Semantic icon colors — each nav item gets a unique color for visual landmarks
const NAV_COLORS = {
dashboard: '#22d3ee', // cyan-400
flows: '#a78bfa', // violet-400
sessions: '#34d399', // emerald-400
exports: '#60a5fa', // blue-400
stepLib: '#fb923c', // orange-400
scripts: '#2dd4bf', // teal-400
analytics: '#38bdf8', // sky-400
guides: '#a3e635', // lime-400
feedback: '#818cf8', // indigo-400
} as const
export function Sidebar() {
const sidebarCollapsed = useUserPreferencesStore(s => s.sidebarCollapsed)
const toggleSidebar = useUserPreferencesStore(s => s.toggleSidebar)
const location = useLocation()
const [stats, setStats] = useState<SidebarStatsResponse | null>(null)
const refreshStats = useCallback(() => {
sidebarApi.getStats().then(setStats).catch(() => {})
}, [])
// Fetch sidebar stats — refreshes on navigation
useEffect(() => {
refreshStats()
}, [location.pathname, refreshStats])
// Refresh when sessions are created or completed
useEffect(() => {
window.addEventListener('session-changed', refreshStats)
return () => window.removeEventListener('session-changed', refreshStats)
}, [refreshStats])
const handleSidebarWheel = (e: React.WheelEvent<HTMLElement>) => {
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 (
<nav
className="sidebar flex flex-col border-r"
style={{
background: 'rgba(16, 17, 20, 0.5)',
backdropFilter: 'var(--glass-blur-light)',
WebkitBackdropFilter: 'var(--glass-blur-light)',
borderColor: 'var(--glass-border)',
}}
onWheel={handleSidebarWheel}
>
{sidebarCollapsed ? (
<>
{/* Collapsed: icon-only nav */}
<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="/sessions" icon={Clock} label="Active Sessions" badge={stats?.active_count || undefined} iconColor={NAV_COLORS.sessions} collapsed />
<NavItem href="/escalations" icon={AlertTriangle} label="Escalations" badge={stats?.escalation_count || undefined} iconColor="#fbbf24" collapsed />
<NavItem href="/trees" icon={Network} label="Flows" matchPaths={['/trees', '/flows', '/my-trees']} iconColor={NAV_COLORS.flows} collapsed />
<NavItem href="/step-library" icon={Library} label="Step Library" iconColor={NAV_COLORS.stepLib} collapsed />
<NavItem href="/scripts" icon={Code2} label="Scripts" iconColor={NAV_COLORS.scripts} collapsed />
<NavItem href="/review-queue" icon={ListChecks} label="Review Queue" iconColor="#fbbf24" 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/flowpilot" icon={TrendingUp} label="FlowPilot Analytics" iconColor="#2dd4bf" 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 />
</div>
</>
) : (
<>
{/* Navigation */}
<div className="px-3 py-2 space-y-0.5">
{/* 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="Active Sessions" badge={stats?.active_count || undefined} iconColor={NAV_COLORS.sessions} matchPaths={['/sessions']} />
<NavItem href="/escalations" icon={AlertTriangle} label="Escalations" badge={stats?.escalation_count || undefined} iconColor="#fbbf24" />
{/* Knowledge */}
<div className="font-label text-[0.5625rem] uppercase tracking-[0.12em] text-[#5a6170] px-3 pt-3 pb-1">
Knowledge
</div>
<NavItem
href="/trees"
icon={Network}
label="Flows"
badge={stats?.tree_counts.total || undefined}
iconColor={NAV_COLORS.flows}
matchPaths={['/trees', '/flows', '/my-trees']}
children={[
{ href: '/trees?type=troubleshooting', label: 'Troubleshooting', count: stats?.tree_counts.troubleshooting || undefined },
{ href: '/trees?type=procedural', label: 'Projects', count: stats?.tree_counts.procedural || undefined },
{ href: '/trees?type=maintenance', label: 'Maintenance', count: stats?.tree_counts.maintenance || undefined },
]}
/>
<NavItem href="/step-library" icon={Library} label="Step Library" iconColor={NAV_COLORS.stepLib} />
<NavItem href="/scripts" icon={Code2} label="Scripts" iconColor={NAV_COLORS.scripts} />
<NavItem href="/review-queue" icon={ListChecks} label="Review Queue" iconColor="#fbbf24" />
{/* 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/flowpilot" icon={TrendingUp} label="FlowPilot Analytics" iconColor="#2dd4bf" />
</div>
</>
)}
{/* Spacer — pushes footer to bottom */}
<div className="flex-1" />
{/* Footer */}
<div
className={cn(
"border-t",
sidebarCollapsed ? "px-1.5 py-2 flex flex-col items-center" : "px-3 py-2 space-y-0.5"
)}
style={{ borderColor: 'var(--glass-border)' }}
>
{!sidebarCollapsed && (
<>
<NavItem href="/guides" icon={BookOpen} label="User Guides" iconColor={NAV_COLORS.guides} />
<NavItem href="/feedback" icon={MessageSquareText} label="Feedback" iconColor={NAV_COLORS.feedback} />
<NavItem href="/account" icon={Settings} label="Account" />
</>
)}
<button
onClick={toggleSidebar}
className={cn(
"flex w-full items-center rounded-lg text-[0.8125rem] font-medium text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground transition-colors",
sidebarCollapsed ? "justify-center p-2.5" : "gap-3 px-3 py-2"
)}
title={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
>
{sidebarCollapsed ? <PanelLeftOpen size={20} /> : <PanelLeftClose size={18} />}
{!sidebarCollapsed && <span>Collapse</span>}
</button>
</div>
</nav>
)
}