@ -70,13 +70,27 @@ export function Dashboard() {
< / div >
< / div >
< / section >
< / section >
{ /* SMS low-balance alert (D28) — surfaced here so it's seen without opening the SMS screen. */ }
{ / * S M S l o w - b a l a n c e a l e r t ( D 2 8 ) — s u r f a c e d h e r e s o i t ' s s e e n w i t h o u t o p e n i n g t h e S M S
{ smsLow . data !== undefined && smsLow . data . lowCount > 0 && (
screen . Three states , never collapsed into one : while the check is in flight a
skeleton stands in ; a FAILED check says so with a retry ( rendering nothing there
would read as "no client is low" — the dangerous direction ) ; and only a loaded ,
genuinely - empty result gets the positive line . ` unknownCount ` is spelled out in
that line because balances the gateway couldn 't return aren' t "fine" either . * / }
{ smsLow . error !== undefined ? (
< ErrorState message = { ` SMS balances could not be checked — ${ smsLow . error } ` } onRetry = { smsLow . reload } / >
) : smsLow . data === undefined ? (
< Skeleton rows = { 1 } / >
) : smsLow . data . lowCount > 0 ? (
< Notice tone = "warn" >
< Notice tone = "warn" >
< Link to = "/sms-balances" style = { { color : 'inherit' , fontWeight : 600 } } >
< Link to = "/sms-balances" style = { { color : 'inherit' , fontWeight : 600 } } >
{ smsLow . data . lowCount } SMS client { smsLow . data . lowCount === 1 ? '' : 's' } low on balance — review & amp ; top up →
{ smsLow . data . lowCount } SMS client { smsLow . data . lowCount === 1 ? '' : 's' } low on balance — review & amp ; top up →
< / Link >
< / Link >
< / Notice >
< / Notice >
) : (
< p className = "dash-cap" >
SMS balances checked — none low
{ smsLow . data . unknownCount > 0 ? ` ( ${ smsLow . data . unknownCount } could not be read) ` : '' } .
< / p >
) }
) }
{ /* KPIs — the app's existing stat cards (kept minimal; red only when a send failed). */ }
{ /* KPIs — the app's existing stat cards (kept minimal; red only when a send failed). */ }
@ -146,11 +160,14 @@ export function Dashboard() {
< / Card >
< / Card >
< Card
< Card
title = "Onboarding stalled" count = { stalled . data ? . length ? ? 0 }
title = "Onboarding stalled"
// Neither a fetch failure nor a still-running fetch is a genuine empty result:
// the error gets an ErrorState with retry, the pending fetch a skeleton, and the
// count/"Nothing stalled." line only appear once the list has actually loaded.
{ . . . ( stalled . data !== undefined ? { count : stalled.data.length } : { } ) }
empty = "Nothing stalled."
empty = "Nothing stalled."
// A fetch failure is not the same as a genuine empty result — show the error
loading = { stalled . data === undefined && stalled . error === undefined }
// (with retry) instead of quietly reporting "Nothing stalled." on failure.
isEmpty = { stalled . data !== undefined && stalled . data . length === 0 }
isEmpty = { stalled . error === undefined && ( stalled . data ? ? [ ] ) . length === 0 }
>
>
{ stalled . error !== undefined
{ stalled . error !== undefined
? < ErrorState message = { stalled . error } onRetry = { stalled . reload } / >
? < ErrorState message = { stalled . error } onRetry = { stalled . reload } / >
@ -182,10 +199,12 @@ export function Dashboard() {
}
}
/ * * O n e b e n t o c a r d : a n u p p e r c a s e - l a b e l l e d h e a d e r ( + c o u n t a n d o p t i o n a l l i n k ) o v e r a
/ * * O n e b e n t o c a r d : a n u p p e r c a s e - l a b e l l e d h e a d e r ( + c o u n t a n d o p t i o n a l l i n k ) o v e r a
* scrollable list , or an empty - state line when there ' s nothing to show . * /
* scrollable list , or an empty - state line when there ' s nothing to show . Cards fed by
* their own request pass ` loading ` so a fetch still in flight shows a skeleton instead
* of the empty line — "nothing to show" must mean the data loaded and was empty . * /
function Card ( props : {
function Card ( props : {
title : string ; count? : number ; link ? : { to : string ; label? : string }
title : string ; count? : number ; link ? : { to : string ; label? : string }
empty : string ; isEmpty : boolean ; children: ReactNode
empty : string ; isEmpty : boolean ; loading?: boolean ; children: ReactNode
} ) {
} ) {
return (
return (
< section className = "dash-card" >
< section className = "dash-card" >
@ -194,7 +213,9 @@ function Card(props: {
{ props . count !== undefined && < span className = "count" > { props . count } < / span > }
{ props . count !== undefined && < span className = "count" > { props . count } < / span > }
{ props . link !== undefined && < Link to = { props . link . to } > { props . link . label ? ? 'All' } → < / Link > }
{ props . link !== undefined && < Link to = { props . link . to } > { props . link . label ? ? 'All' } → < / Link > }
< / div >
< / div >
{ props . isEmpty ? < div className = "dash-empty" > { props . empty } < / div > : < div className = "dash-list" > { props . children } < / div > }
{ props . loading === true ? < div className = "dash-list" > < Skeleton rows = { 2 } / > < / div >
: props . isEmpty ? < div className = "dash-empty" > { props . empty } < / div >
: < div className = "dash-list" > { props . children } < / div > }
< / section >
< / section >
)
)
}
}