Adds 14 new features across the dashboard: - Deal Risk Scoring (0-100) with risk factors on pipeline deals - Account Health Score with multi-factor analysis on account cards - Next Best Action Engine on Executive Overview - Account Notes with pin/edit/delete in activity overlay and account detail - Stakeholder Contact Map with role, sentiment, email, LinkedIn - Competitive Intelligence Tracker with multi-select competitors and filtering - CSV/Excel Export on accounts, activities, and pipeline pages - Data Quality Dashboard in admin with completeness scoring - Campaign Playbook Templates with 5 default plays and account progress tracking - Goals & District Targets with quarterly tracking and progress bars - Weekly Status Report (printable) and Executive Summary (printable) - Activity Effectiveness Scoring showing pipeline conversion by activity type - Mobile Activity FAB for quick activity logging on mobile - Multi-User Auth (NextAuth + Google OAuth, @broadcom.com domain, role-based) Also adds Phase 2 API route, scoring utilities, sidebar navigation updates, and database schema extensions for notes, contacts, snapshots, playbooks, and district targets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
182 lines
9.1 KiB
TypeScript
182 lines
9.1 KiB
TypeScript
'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' },
|
|
{ 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' },
|
|
];
|
|
|
|
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 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>
|
|
</>
|
|
);
|
|
}
|