Build AgentMinder Campaign Command Center - Phase 1 complete
Full-featured sales campaign dashboard replacing Looker Studio with responsive Next.js app. Includes Executive Overview, Account Explorer with priority algorithm, Activities with detail overlay, Pipeline/Opps with deal panels, Implementation tracking, and Data Admin with CSV import and CRUD operations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
145
src/components/layout/Sidebar.tsx
Normal file
145
src/components/layout/Sidebar.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
|
||||
const navItems = [
|
||||
{ href: '/', label: 'Executive Overview', icon: DashboardIcon, shortLabel: 'Overview' },
|
||||
{ href: '/accounts', label: 'Accounts', icon: AccountsIcon, shortLabel: 'Accounts' },
|
||||
{ href: '/activity', label: 'Activities', icon: ActivityIcon, shortLabel: 'Activities' },
|
||||
{ href: '/pipeline', label: 'Opportunities', icon: PipelineIcon, shortLabel: 'Opps' },
|
||||
{ href: '/implementation', label: 'Implementation', icon: ImplementIcon, shortLabel: 'Implement' },
|
||||
];
|
||||
|
||||
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 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>
|
||||
);
|
||||
}
|
||||
|
||||
export function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
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">
|
||||
{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>
|
||||
);
|
||||
})}
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user