import { useState } from 'react' import { NavLink, Outlet } from 'react-router-dom' import { ASK_DESK_ROLES, NAV_GROUPS, STAGES } from '../api/config.js' import { rolesOf, visibleStages } from '../api/permissions.js' import { useStageCounts } from '../api/portfolio.jsx' import { useZino } from '../api/provider.jsx' import AskDesk from '../components/AskDesk.jsx' import UserMenu from './UserMenu.jsx' import logo from '../assets/brand/zurich_logo.webp' import './Shell.css' /** * The frame: a navy masthead, a white rail, a grey canvas. * * The rail groups the pipeline by WHO IS HOLDING each lead — a person, the * customer, the calendar — because "is anything waiting on me?" is the question * anyone signing in actually has. The count on each row is what needs a person; * its colour says how urgently, on the one colour rule the whole console keeps: * red for risk, amber for a person, teal for the customer, blue for the AI. * * Closed lives under History and stays folded: it is reporting, not work. */ /** The badge colour for a queue: the colour of whoever is being waited on. */ function tone(stage) { if (stage.kind === 'needs') return 'red' if (stage.kind === 'customer') return 'amber' return 'grey' } export default function Shell() { const { user } = useZino() const roles = rolesOf(user) const stages = visibleStages(roles) const { counts, ready } = useStageCounts() // On a phone the rail becomes a drawer. It is closed on every navigation, // because a menu that stays open over the page you just chose is a menu you // have to dismiss twice. Desktop never reads this — the rail is always there. const [navOpen, setNavOpen] = useState(false) // The support desk reads the WHOLE book — every partner, every premium — so // it is an ops tool, not something a partner agent may open. The platform // enforces this too (the desk is assigned per user); this only decides // whether the button is drawn. const [deskOpen, setDeskOpen] = useState(false) const canAskDesk = ASK_DESK_ROLES.some((r) => roles.includes(r)) // Closing on the link rather than on the route: a menu that stays open over // the page you just chose is a menu you have to dismiss twice. Keyed off the // anchor so opening the Closed fold — which is not navigation — leaves it up. const closeIfLink = (e) => { if (e.target.closest('a')) setNavOpen(false) } const count = (s) => counts[s.uid] || { total: 0, waiting: 0, working: 0, urgent: 0 } // "All leads" carries the open book, not the whole book. const openTotal = STAGES .filter((s) => s.kind !== 'end') .reduce((t, s) => t + count(s).total, 0) const row = (s) => { const c = count(s) const n = s.kind === 'end' ? c.total : c.waiting const hint = c.working ? `${c.working} being worked by the AI` : undefined return ( 'nav__link' + (isActive ? ' is-on' : '')} > {s.need ?? s.name} {ready ? {n} : null} ) } return (
{/* The lockup as ARTWORK, not as type. It was set in Source Sans with a drawn Z, which is a passable imitation of a brand and not the brand: the real mark's proportions, its weight contrast and its plate are all in the asset. Bundled from src/assets so Vite rewrites the URL and it resolves under the sub-path mount. */} Zurich Kotak General Insurance
{/* The scrim only exists while the drawer is open, and only on a phone: tapping the page you can see should put the menu away. */} {navOpen ?
{deskOpen ? setDeskOpen(false)} /> : null}
) }