Add Phase 3: Analytics & Forecast Intelligence
- Pipeline Movement Tracker with waterfall chart and snapshot capture - Forecast Call Workspace with inline deal overrides and lock history - Forecast Accuracy tracking against locked snapshots - Win/Loss Analysis with rate breakdowns by stage, competitor, deal size, play - Playbook Effectiveness dashboard with conversion funnel and step drop-off - Account engagement trends with 12-week sparklines and direction badges - Analytics sidebar navigation with collapsible submenu - Data layer: forecast_locks, forecast_overrides tables, pipeline movement API - Fix Turbopack function hoisting issues in Sidebar and db.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,18 +4,6 @@ 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">
|
||||
@@ -80,6 +68,14 @@ function ExecIcon({ className }: { className?: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
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">
|
||||
@@ -88,9 +84,31 @@ function AccountsIcon({ className }: { className?: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
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' },
|
||||
];
|
||||
|
||||
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 (
|
||||
<>
|
||||
@@ -116,7 +134,7 @@ export function Sidebar() {
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<nav className="flex-1 py-4 space-y-1">
|
||||
<nav className="flex-1 py-4 space-y-1 overflow-y-auto">
|
||||
{navItems.map(item => {
|
||||
const isActive = pathname === item.href;
|
||||
return (
|
||||
@@ -135,6 +153,50 @@ export function Sidebar() {
|
||||
</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
|
||||
|
||||
Reference in New Issue
Block a user