@@ -518,6 +518,11 @@ export default function Dashboard() {
518518 const [ costAnalysisData , setCostAnalysisData ] = useState < CostReport | null > ( null ) ;
519519 const [ costAnalysisLoading , setCostAnalysisLoading ] = useState ( false ) ;
520520
521+ // --- Session Savings Counter ---
522+ const sessionSavingsRef = useRef < number > ( 0 ) ;
523+ const [ sessionSavingsDisplay , setSessionSavingsDisplay ] = useState < number > ( 0 ) ;
524+ const currentCostRef = useRef < { monthlyCost : number ; fixedCost : number } > ( { monthlyCost : 42 , fixedCost : 166 } ) ;
525+
521526 // --- Public Status / Showcase Banner State ---
522527 const [ publicStatus , setPublicStatus ] = useState < PublicStatus | null > ( null ) ;
523528 const [ toastDismissed , setToastDismissed ] = useState ( true ) ;
@@ -978,6 +983,11 @@ export default function Dashboard() {
978983 const data = await res . json ( ) ;
979984
980985 setCurrent ( data ) ;
986+ // Keep cost ref in sync so the 1s savings counter uses the latest rate
987+ currentCostRef . current = {
988+ monthlyCost : data . cost ?. opGethMonthlyCost ?? data . cost ?. monthlyEstimated ?? 42 ,
989+ fixedCost : data . cost ?. fixedCost ?? 166 ,
990+ } ;
981991
982992 const point = {
983993 name : new Date ( ) . toLocaleTimeString ( [ ] , { hour : '2-digit' , minute : '2-digit' , second : '2-digit' } ) ,
@@ -1117,6 +1127,18 @@ export default function Dashboard() {
11171127 return ( ) => clearInterval ( interval ) ;
11181128 } , [ ] ) ;
11191129
1130+ // --- Session Savings Counter: tick every 1s ---
1131+ useEffect ( ( ) => {
1132+ const ticker = setInterval ( ( ) => {
1133+ const { monthlyCost, fixedCost } = currentCostRef . current ;
1134+ // hourly savings = (fixedCost - monthlyCost) / 730; per second = / 3600
1135+ const savingsPerSecond = ( fixedCost - monthlyCost ) / 730 / 3600 ;
1136+ sessionSavingsRef . current += savingsPerSecond ;
1137+ setSessionSavingsDisplay ( sessionSavingsRef . current ) ;
1138+ } , 1000 ) ;
1139+ return ( ) => clearInterval ( ticker ) ;
1140+ } , [ ] ) ;
1141+
11201142 if ( isLoading ) return (
11211143 < div className = "flex h-screen w-full items-center justify-center bg-gray-50 text-blue-600" >
11221144 < div className = "flex flex-col items-center gap-4" >
@@ -1462,35 +1484,72 @@ export default function Dashboard() {
14621484
14631485 { /* Card 1: Monthly Cost */ }
14641486 < div className = "bg-white rounded-2xl p-5 shadow-sm border border-gray-200/60" >
1465- < div data-testid = "monthly-cost" >
1466- < span className = "text-[11px] text-gray-400 font-semibold uppercase tracking-wider" > Monthly Cost</ span >
1467- < div className = "flex items-baseline gap-2 mt-1" >
1468- < span className = "text-4xl font-black text-gray-900" >
1469- ${ ( current ?. cost . opGethMonthlyCost || current ?. cost . monthlyEstimated || 42 ) . toFixed ( 0 ) }
1470- </ span >
1471- < span className = "text-base font-bold text-gray-400" > /mo</ span >
1472- </ div >
1473- < p className = "text-gray-400 text-[10px] mt-1" >
1474- { current ?. metrics . gethVcpu || 1 } vCPU · { current ?. metrics . gethMemGiB || 2 } GiB
1475- </ p >
1476- </ div >
1477- < div className = "mt-3 pt-3 border-t border-gray-100" >
1478- < div className = "flex items-center justify-between" >
1479- < span className = "text-[10px] text-gray-400" > vs Fixed 4 vCPU (${ current ?. cost . fixedCost ?. toFixed ( 0 ) || '166' } /mo)</ span >
1480- < span className = { `text-xs font-bold ${ current ?. cost . isPeakMode ? 'text-red-500' : 'text-green-600' } ` } >
1481- { current ?. cost . isPeakMode ? '+' : '' } { ( ( current ?. cost . monthlySaving || 0 ) / ( current ?. cost . fixedCost || 166 ) * - 100 ) . toFixed ( 0 ) } %
1482- </ span >
1483- </ div >
1484- < div className = "mt-2 h-2 bg-gray-100 rounded-full overflow-hidden" >
1485- < div
1486- className = { `h-full rounded-full transition-all duration-500 ${ current ?. cost . isPeakMode ? 'bg-red-400' : 'bg-green-400' } ` }
1487- style = { { width : `${ Math . min ( Math . max ( ( ( current ?. cost . opGethMonthlyCost || 42 ) / ( current ?. cost . fixedCost || 166 ) ) * 100 , 5 ) , 100 ) } %` } }
1488- />
1489- </ div >
1490- < div className = "flex justify-between mt-1" >
1491- < span className = "text-[9px] text-gray-400" > $0</ span >
1492- < span className = "text-[9px] text-gray-400" > ${ current ?. cost . fixedCost ?. toFixed ( 0 ) || '166' } </ span >
1493- </ div >
1487+ { ( ( ) => {
1488+ const monthlyCost = current ?. cost . opGethMonthlyCost ?? current ?. cost . monthlyEstimated ?? 42 ;
1489+ const fixedCost = current ?. cost . fixedCost ?? 166 ;
1490+ const vcpu = current ?. metrics . gethVcpu ?? 1 ;
1491+ const memGiB = current ?. metrics . gethMemGiB ?? 2 ;
1492+ const isPeak = current ?. cost . isPeakMode ?? false ;
1493+ const hourlyRate = monthlyCost / 730 ;
1494+ const baselineHourly = fixedCost / 730 ;
1495+ const savingsPct = ( ( fixedCost - monthlyCost ) / fixedCost * 100 ) ;
1496+ const barPct = Math . min ( Math . max ( ( monthlyCost / fixedCost ) * 100 , 5 ) , 100 ) ;
1497+ const scenarioLabel = vcpu >= 8 ? { text : 'Emergency' , color : 'text-red-500 bg-red-50' }
1498+ : vcpu >= 4 ? { text : 'High Load' , color : 'text-orange-500 bg-orange-50' }
1499+ : vcpu >= 2 ? { text : 'Moderate' , color : 'text-amber-500 bg-amber-50' }
1500+ : { text : 'Optimized' , color : 'text-emerald-600 bg-emerald-50' } ;
1501+ const sessionAbs = Math . abs ( sessionSavingsDisplay ) ;
1502+ const sessionIsOverspend = sessionSavingsDisplay < - 0.001 ;
1503+ return (
1504+ < >
1505+ < div data-testid = "monthly-cost" >
1506+ < div className = "flex items-start justify-between" >
1507+ < span className = "text-[11px] text-gray-400 font-semibold uppercase tracking-wider" > Monthly Cost</ span >
1508+ < span className = { `text-[10px] font-semibold px-1.5 py-0.5 rounded-full ${ scenarioLabel . color } ` } >
1509+ { scenarioLabel . text }
1510+ </ span >
1511+ </ div >
1512+ < div className = "flex items-baseline gap-2 mt-1" >
1513+ < span className = "text-4xl font-black text-gray-900" >
1514+ ${ monthlyCost . toFixed ( 0 ) }
1515+ </ span >
1516+ < span className = "text-base font-bold text-gray-400" > /mo</ span >
1517+ < span className = { `ml-auto text-[11px] font-semibold ${ isPeak ? 'text-red-500' : 'text-gray-500' } ` } >
1518+ ${ hourlyRate . toFixed ( 3 ) } /hr
1519+ </ span >
1520+ </ div >
1521+ < p className = "text-gray-400 text-[10px] mt-0.5" >
1522+ { vcpu } vCPU · { memGiB } GiB · baseline ${ baselineHourly . toFixed ( 3 ) } /hr
1523+ </ p >
1524+ </ div >
1525+ < div className = "mt-3 pt-3 border-t border-gray-100" >
1526+ < div className = "flex items-center justify-between" >
1527+ < span className = "text-[10px] text-gray-400" > vs Fixed 4 vCPU (${ fixedCost . toFixed ( 0 ) } /mo)</ span >
1528+ < span className = { `text-xs font-bold ${ isPeak ? 'text-red-500' : 'text-green-600' } ` } >
1529+ { isPeak ? '+' : '-' } { Math . abs ( savingsPct ) . toFixed ( 0 ) } %
1530+ </ span >
1531+ </ div >
1532+ < div className = "mt-2 h-2 bg-gray-100 rounded-full overflow-hidden" >
1533+ < div
1534+ className = { `h-full rounded-full transition-all duration-700 ${ isPeak ? 'bg-red-400' : 'bg-emerald-400' } ` }
1535+ style = { { width : `${ barPct } %` } }
1536+ />
1537+ </ div >
1538+ < div className = "flex justify-between mt-1" >
1539+ < span className = "text-[9px] text-gray-400" > $0</ span >
1540+ < span className = "text-[9px] text-gray-400" > ${ fixedCost . toFixed ( 0 ) } </ span >
1541+ </ div >
1542+ { /* Session Savings Counter */ }
1543+ < div className = { `mt-2.5 flex items-center justify-between px-2.5 py-1.5 rounded-lg ${ sessionIsOverspend ? 'bg-red-50' : 'bg-emerald-50' } ` } >
1544+ < span className = "text-[10px] text-gray-500" > Session { sessionIsOverspend ? 'overspend' : 'savings' } </ span >
1545+ < span className = { `text-[11px] font-bold tabular-nums ${ sessionIsOverspend ? 'text-red-600' : 'text-emerald-600' } ` } >
1546+ { sessionIsOverspend ? '+' : '-' } ${ sessionAbs . toFixed ( 4 ) }
1547+ </ span >
1548+ </ div >
1549+ </ div >
1550+ </ >
1551+ ) ;
1552+ } ) ( ) }
14941553 < button
14951554 onClick = { ( ) => {
14961555 if ( costAnalysisExpanded ) {
@@ -1571,7 +1630,6 @@ export default function Dashboard() {
15711630 ) }
15721631 </ div >
15731632 ) }
1574- </ div >
15751633 </ div >
15761634 </ div >
15771635
0 commit comments