Files
campaign-command-center/src/components/layout/Sidebar.tsx
Chris Olson 3cc4902ce2 Integrate BSG Prospecting Loop into Campaign Command Center
Add full leads pipeline: Lead/LeadRun types, 3 new DB tables (leads,
leads_runs, leads_config), 12 CRUD functions including syncBriefsFromDisk
and exportAccountsForLoop, 12 API actions for leads lifecycle management.

New pages: /leads (filterable lead cards with expand/action), /leads/admin
(run controls, config, run history). AccountLeads component in account
detail view. New Leads scorecard on Executive Overview. Sidebar nav entry.

Also fixes account detail activity list using raw.activities to show all
activities regardless of date filter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-09 12:03:48 -04:00

253 lines
13 KiB
TypeScript

'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useState } from 'react';
function DashboardIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="3" y="3" width="7" height="7" rx="1" /><rect x="14" y="3" width="7" height="4" rx="1" /><rect x="14" y="10" width="7" height="11" rx="1" /><rect x="3" y="13" width="7" height="8" rx="1" />
</svg>
);
}
function ActivityIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
</svg>
);
}
function PipelineIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<line x1="12" y1="20" x2="12" y2="10" /><line x1="18" y1="20" x2="18" y2="4" /><line x1="6" y1="20" x2="6" y2="16" />
</svg>
);
}
function ImplementIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" /><polyline points="22 4 12 14.01 9 11.01" />
</svg>
);
}
function PlaybookIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" /><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
</svg>
);
}
function GoalsIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" /><circle cx="12" cy="12" r="6" /><circle cx="12" cy="12" r="2" />
</svg>
);
}
function ReportsIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><polyline points="14 2 14 8 20 8" /><line x1="16" y1="13" x2="8" y2="13" /><line x1="16" y1="17" x2="8" y2="17" /><polyline points="10 9 9 9 8 9" />
</svg>
);
}
function ExecIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2" /><line x1="8" y1="21" x2="16" y2="21" /><line x1="12" y1="17" x2="12" y2="21" />
</svg>
);
}
function AnalyticsIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" /><polyline points="7.5 4.21 12 6.81 16.5 4.21" /><polyline points="7.5 19.79 7.5 14.6 3 12" /><polyline points="21 12 16.5 14.6 16.5 19.79" /><polyline points="3.27 6.96 12 12.01 20.73 6.96" /><line x1="12" y1="22.08" x2="12" y2="12" />
</svg>
);
}
function AccountsIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M23 21v-2a4 4 0 0 0-3-3.87" /><path d="M16 3.13a4 4 0 0 1 0 7.75" />
</svg>
);
}
function LeadsIcon({ className }: { className?: string }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
</svg>
);
}
const navItems = [
{ href: '/', label: 'Executive Overview', icon: DashboardIcon, shortLabel: 'Overview' },
{ href: '/accounts', label: 'Accounts', icon: AccountsIcon, shortLabel: 'Accounts' },
{ href: '/leads', label: 'Leads', icon: LeadsIcon, shortLabel: 'Leads' },
{ href: '/activity', label: 'Activities', icon: ActivityIcon, shortLabel: 'Activities' },
{ href: '/pipeline', label: 'Opportunities', icon: PipelineIcon, shortLabel: 'Opps' },
{ href: '/implementation', label: 'Implementation', icon: ImplementIcon, shortLabel: 'Implement' },
{ href: '/goals', label: 'Goals & Targets', icon: GoalsIcon, shortLabel: 'Goals' },
{ href: '/playbooks', label: 'Playbooks', icon: PlaybookIcon, shortLabel: 'Plays' },
{ href: '/reports/status', label: 'Weekly Report', icon: ReportsIcon, shortLabel: 'Report' },
{ href: '/reports/executive', label: 'Exec Summary', icon: ExecIcon, shortLabel: 'Summary' },
];
const analyticsItems = [
{ href: '/analytics/pipeline-movement', label: 'Pipeline Movement' },
{ href: '/analytics/forecast', label: 'Forecast Workspace' },
{ href: '/analytics/forecast-accuracy', label: 'Forecast Accuracy' },
{ href: '/analytics/win-loss', label: 'Win/Loss Analysis' },
{ href: '/analytics/playbook-effectiveness', label: 'Playbook Effectiveness' },
];
export function Sidebar() {
const pathname = usePathname();
const [collapsed, setCollapsed] = useState(false);
const [analyticsOpen, setAnalyticsOpen] = useState(pathname.startsWith('/analytics'));
const isAnalyticsActive = pathname.startsWith('/analytics');
return (
<>
{/* Desktop sidebar */}
<aside className={`hidden lg:flex flex-col bg-sidebar-bg text-sidebar-text ${collapsed ? 'w-16' : 'w-56'} transition-all duration-200 min-h-screen`}>
<div className={`flex items-center ${collapsed ? 'justify-center px-2' : 'px-4'} h-16 border-b border-white/10`}>
{!collapsed && (
<Link href="/" className="flex items-center gap-2">
<img src="/logo.svg" alt="Logo" className="h-8 w-8" onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }} />
<div>
<div className="text-white font-bold text-sm tracking-wide">AgentMinder</div>
<div className="text-[10px] text-sidebar-text/60 tracking-wider uppercase">Command Center</div>
</div>
</Link>
)}
<button
onClick={() => setCollapsed(!collapsed)}
className={`${collapsed ? '' : 'ml-auto'} p-1 rounded hover:bg-white/10 transition`}
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
>
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
{collapsed ? <polyline points="9 18 15 12 9 6" /> : <polyline points="15 18 9 12 15 6" />}
</svg>
</button>
</div>
<nav className="flex-1 py-4 space-y-1 overflow-y-auto">
{navItems.map(item => {
const isActive = pathname === item.href;
return (
<Link
key={item.href}
href={item.href}
className={`flex items-center gap-3 ${collapsed ? 'justify-center px-2' : 'px-4'} py-2.5 mx-2 rounded-lg transition-all ${
isActive
? 'bg-sidebar-active/20 text-white'
: 'hover:bg-white/5 text-sidebar-text hover:text-white'
}`}
title={collapsed ? item.label : undefined}
>
<item.icon className={`w-5 h-5 flex-shrink-0 ${isActive ? 'text-sidebar-active' : ''}`} />
{!collapsed && <span className="text-sm font-medium">{item.label}</span>}
</Link>
);
})}
{/* Analytics section */}
{!collapsed ? (
<>
<button
onClick={() => setAnalyticsOpen(!analyticsOpen)}
className={`flex items-center gap-3 px-4 py-2.5 mx-2 rounded-lg transition-all w-full text-left ${
isAnalyticsActive ? 'bg-sidebar-active/20 text-white' : 'hover:bg-white/5 text-sidebar-text hover:text-white'
}`}
>
<AnalyticsIcon className={`w-5 h-5 flex-shrink-0 ${isAnalyticsActive ? 'text-sidebar-active' : ''}`} />
<span className="text-sm font-medium flex-1">Analytics</span>
<svg className={`w-3.5 h-3.5 transition-transform ${analyticsOpen ? 'rotate-90' : ''}`} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="9 18 15 12 9 6" /></svg>
</button>
{analyticsOpen && (
<div className="ml-8 mr-2 space-y-0.5">
{analyticsItems.map(item => {
const isActive = pathname === item.href;
return (
<Link
key={item.href}
href={item.href}
className={`block px-3 py-1.5 rounded-md text-xs font-medium transition-all ${
isActive ? 'bg-sidebar-active/20 text-white' : 'text-sidebar-text/70 hover:text-white hover:bg-white/5'
}`}
>
{item.label}
</Link>
);
})}
</div>
)}
</>
) : (
<Link
href="/analytics/pipeline-movement"
className={`flex items-center justify-center px-2 py-2.5 mx-2 rounded-lg transition-all ${
isAnalyticsActive ? 'bg-sidebar-active/20 text-white' : 'hover:bg-white/5 text-sidebar-text hover:text-white'
}`}
title="Analytics"
>
<AnalyticsIcon className={`w-5 h-5 flex-shrink-0 ${isAnalyticsActive ? 'text-sidebar-active' : ''}`} />
</Link>
)}
</nav>
<div className={`${collapsed ? 'px-2' : 'px-4'} pb-4 space-y-3`}>
<Link
href="/admin"
className={`flex items-center gap-2 ${collapsed ? 'justify-center px-2' : 'px-3'} py-2 rounded-lg text-sidebar-text/60 hover:text-white hover:bg-white/5 transition text-xs`}
title={collapsed ? 'Data Admin' : undefined}
>
<svg className="w-4 h-4 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="3" /><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
</svg>
{!collapsed && <span>Data Admin</span>}
</Link>
<div className={`rounded-lg bg-white/5 ${collapsed ? 'p-2' : 'p-3'}`}>
{!collapsed && (
<div className="text-[10px] uppercase tracking-wider text-sidebar-text/50 mb-1">Territory</div>
)}
<div className={`text-xs text-white font-medium ${collapsed ? 'text-center' : ''}`}>
{collapsed ? 'SE' : 'SouthEast'}
</div>
</div>
</div>
</aside>
{/* Mobile bottom nav */}
<nav className="lg:hidden fixed bottom-0 left-0 right-0 bg-white border-t border-card-border z-50 flex">
{navItems.map(item => {
const isActive = pathname === item.href;
return (
<Link
key={item.href}
href={item.href}
className={`flex-1 flex flex-col items-center py-2 text-[10px] font-medium transition ${
isActive ? 'text-brand-navy' : 'text-muted'
}`}
>
<item.icon className={`w-5 h-5 mb-0.5 ${isActive ? 'text-brand-navy' : 'text-muted'}`} />
{item.shortLabel}
</Link>
);
})}
</nav>
</>
);
}