import { useEffect, useMemo, useState } from 'react'; import { Outlet, useLocation, useNavigate } from 'react-router-dom'; import type { LucideIcon } from 'lucide-react'; import { ArrowLeft, CalendarClock, FileText, LogOut, Menu, ShieldCheck, Sparkles, UserCheck, Users, X } from 'lucide-react'; import { cn } from '../lib/cn'; import { Avatar } from '../components/core'; import { ZinoLogo } from '../components/ZinoLogo'; import { canSeeScreen } from '../api/config'; import { useZino } from '../api/provider'; import { useLeads } from '../api/leads'; import type { Lead } from '../types'; interface NavItem { to: string; label: string; Icon: LucideIcon; title: string; subtitle: string; } const NAV: NavItem[] = [ { to: '/pipeline', label: 'Lead Pipeline', Icon: Users, title: 'Lead pipeline', subtitle: '' }, { to: '/schedule', label: 'Agent Schedule', Icon: CalendarClock, title: 'Agent schedule', subtitle: 'Sales-agent meetings & booking slots' }, { to: '/qualify', label: 'Qualify & Assign', Icon: UserCheck, title: 'Qualification & Assignment', subtitle: 'Qualify new leads and assign them to an agent' }, { to: '/engage', label: 'Customer Interaction', Icon: Sparkles, title: 'Customer Interaction', subtitle: 'Log contact, schedule meetings, recommend products' }, { to: '/documents', label: 'Documents', Icon: FileText, title: 'Document & Assessment', subtitle: 'KYC collection, OCR, eligibility assessment' }, { to: '/underwriting', label: 'Underwriting & Policy', Icon: ShieldCheck, title: 'Underwriting & Policy Issuance', subtitle: 'Underwrite, issue policy, onboard customer' }, ]; // Pipeline nav subtitle, computed live: "N leads · X% hot · Y% AI-handled". function pipelineSubtitle(leads: Lead[]): string { if (!leads.length) return 'No leads yet'; const total = leads.length; const hot = Math.round((leads.filter((l) => l.segment === 'Hot').length / total) * 100); const ai = Math.round((leads.filter((l) => l.ownerAi).length / total) * 100); return `${total.toLocaleString('en-IN')} leads · ${hot}% hot · ${ai}% AI-handled`; } function BrandLogo() { return (
); } /** Sidebar contents — shared between the static desktop rail and the mobile drawer. */ function SidebarInner({ items, activeTo, onNav }: { items: NavItem[]; activeTo: string; onNav: (to: string) => void }) { return ( <>
{/* live AI-employee status — pinned to the bottom */}
AI employees active
Qualify · Engage · Underwrite
); } function Topbar({ title, subtitle, onMenu, showBack }: { title: string; subtitle: string; onMenu: () => void; showBack: boolean }) { const { user, logout } = useZino(); const navigate = useNavigate(); return (
{showBack && ( )}

{title}

{subtitle &&
{subtitle}
}
{user?.name || 'User'}
{user?.email || ''}
); } /** App shell — navy sidebar (nav) + topbar, with routed content. */ export function Shell() { const location = useLocation(); const navigate = useNavigate(); const { leads } = useLeads(); const { user } = useZino(); const path = location.pathname; // Nav is RBAC-filtered: a user only sees screens their roles allow. const visibleNav = useMemo(() => NAV.filter((n) => canSeeScreen(user?.roles, n.to)), [user]); const navMatch = NAV.find((n) => path.startsWith(n.to)); // Routes reached contextually (no sidebar entry) still get a topbar title. const active = navMatch ?? (path.startsWith('/leads') ? { to: '/leads', label: 'Lead 360', Icon: Users, title: 'Lead 360', subtitle: 'Full lead context, AI decision stream & next action' } : path.startsWith('/new') ? { to: '/new', label: 'New lead', Icon: Users, title: 'Capture lead', subtitle: 'Add a new lead to the pipeline' } : NAV[0]); const subtitle = active.to === '/pipeline' ? pipelineSubtitle(leads) : active.subtitle; const [navOpen, setNavOpen] = useState(false); // Close the mobile drawer whenever the route changes. useEffect(() => setNavOpen(false), [path]); return (
{/* Static rail — lg and up. Pinned; the page scrolls normally beside it. */} {/* Drawer — below lg. */} {navOpen && (
setNavOpen(false)} />
)}
setNavOpen(true)} showBack={!navMatch} />
); }