@ -5,8 +5,8 @@ import {
avatarColors , Badge , CommandPalette , initials , ModeToggle , type PaletteItem ,
avatarColors , Badge , CommandPalette , initials , ModeToggle , type PaletteItem ,
} from '@sims/ui'
} from '@sims/ui'
import {
import {
clearSession , displayName , get EmailStatus, getPipeline , getReminders , getRenewals ,
clearSession , displayName , get Clients, getDocuments , getEmailStatus , getModules , getPipeline ,
get Search, getSmsBalances , getTickets , hasSession , role ,
get Reminders, getRenewals , get Search, getSmsBalances , getTickets , hasSession , role ,
} from './api'
} from './api'
import { NAV_GROUPS } from './nav'
import { NAV_GROUPS } from './nav'
@ -24,8 +24,10 @@ export function Layout() {
return ! v
return ! v
} )
} )
const [ queueCounts , setQueueCounts ] = useState ( { queued : 0 , failed : 0 } )
const [ queueCounts , setQueueCounts ] = useState ( { queued : 0 , failed : 0 } )
// Section-badge counts (Pipeline / Tickets / Renewals / SMS) — fetched once on mount.
// Section-badge counts — fetched once on mount (best-effort, stay 0 on failure).
const [ sectionCounts , setSectionCounts ] = useState ( { pipeline : 0 , tickets : 0 , renewals : 0 , smsLow : 0 } )
const [ sectionCounts , setSectionCounts ] = useState ( {
pipeline : 0 , tickets : 0 , renewals : 0 , smsLow : 0 , clients : 0 , docDrafts : 0 , module s : 0 ,
} )
const [ healthy , setHealthy ] = useState < boolean | undefined > ( )
const [ healthy , setHealthy ] = useState < boolean | undefined > ( )
const [ menuOpen , setMenuOpen ] = useState ( false )
const [ menuOpen , setMenuOpen ] = useState ( false )
const [ userOpen , setUserOpen ] = useState ( false )
const [ userOpen , setUserOpen ] = useState ( false )
@ -60,6 +62,9 @@ export function Layout() {
getTickets ( { status : 'open' } ) . then ( ( r ) = > setSectionCounts ( ( c ) = > ( { . . . c , tickets : r.total } ) ) ) . catch ( ( ) = > { /* best-effort */ } )
getTickets ( { status : 'open' } ) . then ( ( r ) = > setSectionCounts ( ( c ) = > ( { . . . c , tickets : r.total } ) ) ) . catch ( ( ) = > { /* best-effort */ } )
getRenewals ( ) . then ( ( rows ) = > setSectionCounts ( ( c ) = > ( { . . . c , renewals : rows.length } ) ) ) . catch ( ( ) = > { /* best-effort */ } )
getRenewals ( ) . then ( ( rows ) = > setSectionCounts ( ( c ) = > ( { . . . c , renewals : rows.length } ) ) ) . catch ( ( ) = > { /* best-effort */ } )
getSmsBalances ( ) . then ( ( r ) = > setSectionCounts ( ( c ) = > ( { . . . c , smsLow : r.lowCount } ) ) ) . catch ( ( ) = > { /* best-effort */ } )
getSmsBalances ( ) . then ( ( r ) = > setSectionCounts ( ( c ) = > ( { . . . c , smsLow : r.lowCount } ) ) ) . catch ( ( ) = > { /* best-effort */ } )
getClients ( ) . then ( ( cs ) = > setSectionCounts ( ( c ) = > ( { . . . c , clients : cs.length } ) ) ) . catch ( ( ) = > { /* best-effort */ } )
getDocuments ( { status : 'draft' , pageSize : 1 } ) . then ( ( r ) = > setSectionCounts ( ( c ) = > ( { . . . c , docDrafts : r.total } ) ) ) . catch ( ( ) = > { /* best-effort */ } )
getModules ( ) . then ( ( ms ) = > setSectionCounts ( ( c ) = > ( { . . . c , module s : ms . length } ) ) ) . catch ( ( ) = > { /* best-effort */ } )
} , [ ] )
} , [ ] )
// Close the mobile drawer and the user menu when the route changes.
// Close the mobile drawer and the user menu when the route changes.
@ -165,6 +170,9 @@ export function Layout() {
if ( sectionCounts . tickets > 0 ) counts [ '/tickets' ] = { n : sectionCounts.tickets , title : ` ${ sectionCounts . tickets } open ` }
if ( sectionCounts . tickets > 0 ) counts [ '/tickets' ] = { n : sectionCounts.tickets , title : ` ${ sectionCounts . tickets } open ` }
if ( sectionCounts . renewals > 0 ) counts [ '/renewals' ] = { n : sectionCounts.renewals , title : ` ${ sectionCounts . renewals } upcoming ` }
if ( sectionCounts . renewals > 0 ) counts [ '/renewals' ] = { n : sectionCounts.renewals , title : ` ${ sectionCounts . renewals } upcoming ` }
if ( sectionCounts . smsLow > 0 ) counts [ '/sms-balances' ] = { n : sectionCounts.smsLow , tone : 'warn' , title : ` ${ sectionCounts . smsLow } low on balance ` }
if ( sectionCounts . smsLow > 0 ) counts [ '/sms-balances' ] = { n : sectionCounts.smsLow , tone : 'warn' , title : ` ${ sectionCounts . smsLow } low on balance ` }
if ( sectionCounts . clients > 0 ) counts [ '/clients' ] = { n : sectionCounts.clients , title : ` ${ sectionCounts . clients } client ${ sectionCounts . clients === 1 ? '' : 's' } ` }
if ( sectionCounts . docDrafts > 0 ) counts [ '/documents' ] = { n : sectionCounts.docDrafts , title : ` ${ sectionCounts . docDrafts } draft ${ sectionCounts . docDrafts === 1 ? '' : 's' } to finish ` }
if ( sectionCounts . module s > 0 ) counts [ '/modules' ] = { n : sectionCounts.modules , title : ` ${ sectionCounts . module s } module ${ sectionCounts . module s === 1 ? '' : 's' } ` }
return (
return (
< div className = { ` hq-shell ${ collapsed ? ' side-collapsed' : '' } ` } >
< div className = { ` hq-shell ${ collapsed ? ' side-collapsed' : '' } ` } >