@ -1,25 +1,22 @@
import { Link } from 'react-router-dom'
import {
BarChart3 , BellRing , Boxes , CalendarClock , Files , Filter , Gauge , KeyRound , LayoutDashboard ,
ListChecks , MessageSquare , ScrollText , Settings as SettingsIcon , Table2 , Upload , UserCog , Users , Wrench ,
BellRing , Boxes , CalendarClock , Files , Filter , LayoutDashboard , MessageSquare , Users , Wrench ,
type LucideIcon ,
} from 'lucide-react'
import { displayName , getDashboard , getPipeline , getSmsBalances , getTickets , role } from '../api'
import { displayName , getDashboard , getPipeline , getSmsBalances , getTickets } from '../api'
import { useData } from './Clients'
type Tone = 'warn' | 'err'
interface Stat { text : string ; tone? : Tone }
interface HubCard { to : string ; label : string ; desc : string ; icon : LucideIcon ; ownerOnly?: boolean ; stat?: Stat }
interface HubCard { to : string ; label : string ; desc : string ; icon : LucideIcon ; stat?: Stat }
/ * *
* Home hub ( D30 ) — a card launcher for every part of the console , grouped like the
* sidebar . Each card carries a one - line purpose and , where cheap to fetch , a live
* count so the landing doubles as an at - a - glance status board . Owner - only areas are
* hidden for other roles ( the routes are gated server - side regardless ) .
* Home hub ( D30 ) — a quick - access launcher for the sections used day to day . Not
* every screen ( the sidebar has those ) ; just the main ones , each a big target with
* a live count so the landing doubles as a status board .
* /
export function Home() {
const name = displayName ( )
const isOwner = role ( ) === 'owner'
const dash = useData ( ( ) = > getDashboard ( false ) , [ ] )
const sms = useData ( getSmsBalances , [ ] )
const tickets = useData ( ( ) = > getTickets ( { status : 'open' } ) , [ ] )
@ -38,82 +35,40 @@ export function Home() {
const pipeStat : Stat | undefined = pipeline . data !== undefined && pipeline . data . total > 0
? { text : ` ${ pipeline . data . total } open ` } : undefined
const groups : { label : string ; cards : HubCard [ ] } [ ] = [
{
label : 'Overview' ,
cards : [
{ to : '/' , label : 'Dashboard' , desc : 'Today’ s money — dues, renewals, follow-ups, the send queue.' , icon : LayoutDashboard , stat : { text : 'My Day' } } ,
] ,
} ,
{
label : 'Work' ,
cards : [
{ to : '/pipeline' , label : 'Pipeline' , desc : 'Leads and quotes in flight, with next-action nudges.' , icon : Filter , . . . ( pipeStat !== undefined ? { stat : pipeStat } : { } ) } ,
{ to : '/reminders' , label : 'Reminders' , desc : 'The email send queue — preview, send, dismiss.' , icon : BellRing , . . . ( remStat !== undefined ? { stat : remStat } : { } ) } ,
{ to : '/tickets' , label : 'Tickets' , desc : 'Support desk with SLA aging and overdue flags.' , icon : Wrench , . . . ( ticketStat !== undefined ? { stat : ticketStat } : { } ) } ,
{ to : '/projects' , label : 'Onboarding' , desc : 'Install and training checklists across active projects.' , icon : ListChecks } ,
] ,
} ,
{
label : 'Clients & billing' ,
cards : [
{ to : '/clients' , label : 'Clients' , desc : 'The client book — profiles, modules, ledger, support.' , icon : Users } ,
{ to : '/documents' , label : 'Documents' , desc : 'Quotations, proformas, invoices, credit notes.' , icon : Files } ,
{ to : '/renewals' , label : 'Renewals' , desc : 'AMC and module renewals — one-click renewal quotes.' , icon : CalendarClock , . . . ( renewStat !== undefined ? { stat : renewStat } : { } ) } ,
{ to : '/sms-balances' , label : 'SMS credits' , desc : 'Client SMS balances and top-ups.' , icon : MessageSquare , . . . ( smsStat !== undefined ? { stat : smsStat } : { } ) } ,
] ,
} ,
{
label : 'Catalog & insight' ,
cards : [
{ to : '/modules' , label : 'Modules' , desc : 'Sellable modules with the dated price book.' , icon : Boxes } ,
{ to : '/module-data' , label : 'Module data' , desc : 'Operational service data across all client modules.' , icon : Table2 } ,
{ to : '/portals' , label : 'Portals' , desc : 'Client portal logins and credentials.' , icon : KeyRound } ,
{ to : '/mis' , label : 'MIS cockpit' , desc : 'Owner overview — invoiced, collected, outstanding.' , icon : Gauge , ownerOnly : true } ,
{ to : '/reports' , label : 'Reports' , desc : 'Dues aging, GST summary, module revenue, profitability.' , icon : BarChart3 } ,
] ,
} ,
{
label : 'Admin' ,
cards : [
{ to : '/employees' , label : 'Employees' , desc : 'Staff accounts, roles and access.' , icon : UserCog , ownerOnly : true } ,
{ to : '/import' , label : 'APEX import' , desc : 'Bring in legacy clients and invoices from APEX.' , icon : Upload , ownerOnly : true } ,
{ to : '/audit' , label : 'Audit log' , desc : 'Every change, append-only.' , icon : ScrollText , ownerOnly : true } ,
{ to : '/settings' , label : 'Settings' , desc : 'Company, letterhead, reminders, appearance.' , icon : SettingsIcon } ,
] ,
} ,
// Main, look-at-it-all-the-time sections only — the rest live in the sidebar.
const cards : HubCard [ ] = [
{ to : '/' , label : 'Dashboard' , desc : 'Today’ s money — dues, renewals, follow-ups, the send queue.' , icon : LayoutDashboard , stat : { text : 'My Day' } } ,
{ to : '/clients' , label : 'Clients' , desc : 'The client book — profiles, modules, ledger, support.' , icon : Users } ,
{ to : '/documents' , label : 'Documents' , desc : 'Quotations, proformas, invoices, credit notes.' , icon : Files } ,
{ to : '/reminders' , label : 'Reminders' , desc : 'The email send queue — preview, send, dismiss.' , icon : BellRing , . . . ( remStat !== undefined ? { stat : remStat } : { } ) } ,
{ to : '/renewals' , label : 'Renewals' , desc : 'AMC and module renewals — one-click renewal quotes.' , icon : CalendarClock , . . . ( renewStat !== undefined ? { stat : renewStat } : { } ) } ,
{ to : '/pipeline' , label : 'Pipeline' , desc : 'Leads and quotes in flight, with next-action nudges.' , icon : Filter , . . . ( pipeStat !== undefined ? { stat : pipeStat } : { } ) } ,
{ to : '/tickets' , label : 'Tickets' , desc : 'Support desk with SLA aging and overdue flags.' , icon : Wrench , . . . ( ticketStat !== undefined ? { stat : ticketStat } : { } ) } ,
{ to : '/sms-balances' , label : 'SMS credits' , desc : 'Client SMS balances and top-ups.' , icon : MessageSquare , . . . ( smsStat !== undefined ? { stat : smsStat } : { } ) } ,
{ to : '/modules' , label : 'Modules' , desc : 'Sellable modules with the dated price book.' , icon : Boxes } ,
]
return (
< div className = "wf-page" >
< div className = "hub-head" >
< h1 > Welcome back { name !== '' ? ` , ${ name } ` : '' } < / h1 >
< p > Jump straight to any part of the console . < / p >
< p > Your most - used areas — everything else is in the sidebar . < / p >
< / div >
< div className = "hub-cards" >
{ cards . map ( ( c ) = > (
< Link key = { c . to } to = { c . to } className = "hub-card" >
< span className = "hub-ic" > < c.icon size = { 18 } strokeWidth = { 1.9 } aria - hidden / > < / span >
< span className = "hub-body" >
< span className = "hub-t" > { c . label } < / span >
< span className = "hub-d" > { c . desc } < / span >
{ c . stat !== undefined && (
< span className = { ` hub-stat ${ c . stat . tone !== undefined ? ` ${ c . stat . tone } ` : '' } ` } > { c . stat . text } < / span >
) }
< / span >
< span className = "hub-arrow" aria - hidden > → < / span >
< / Link >
) ) }
< / div >
{ groups . map ( ( g ) = > {
const cards = g . cards . filter ( ( c ) = > c . ownerOnly !== true || isOwner )
if ( cards . length === 0 ) return null
return (
< section className = "hub-section" key = { g . label } >
< div className = "hub-eyebrow" > { g . label } < / div >
< div className = "hub-cards" >
{ cards . map ( ( c ) = > (
< Link key = { c . to } to = { c . to } className = "hub-card" >
< span className = "hub-ic" > < c.icon size = { 18 } strokeWidth = { 1.9 } aria - hidden / > < / span >
< span className = "hub-body" >
< span className = "hub-t" > { c . label } < / span >
< span className = "hub-d" > { c . desc } < / span >
{ c . stat !== undefined && (
< span className = { ` hub-stat ${ c . stat . tone !== undefined ? ` ${ c . stat . tone } ` : '' } ` } > { c . stat . text } < / span >
) }
< / span >
< span className = "hub-arrow" aria - hidden > → < / span >
< / Link >
) ) }
< / div >
< / section >
)
} ) }
< / div >
)
}