11import { useState , useEffect , useRef , useCallback } from "react" ;
22import type { ActiveSession , DayEntry , PeriodStats , WsMessage } from "./types" ;
3+ import { OverviewDashboard } from "./components/OverviewDashboard" ;
34import { LiveSessions } from "./components/LiveSessions" ;
45import { StatsTabs } from "./components/StatsTabs" ;
5- import { RateLimits } from "./components/RateLimits" ;
66import { AnalyticsTabs } from "./components/AnalyticsTabs" ;
77
88type Period = "today" | "week" | "month" ;
99type WsStatus = "connecting" | "connected" | "disconnected" ;
10+ type View = "dashboard" | "analytics" ;
1011
1112export function App ( ) {
1213 const [ sessions , setSessions ] = useState < ActiveSession [ ] > ( [ ] ) ;
@@ -15,6 +16,7 @@ export function App() {
1516 const [ period , setPeriod ] = useState < Period > ( "today" ) ;
1617 const [ wsStatus , setWsStatus ] = useState < WsStatus > ( "connecting" ) ;
1718 const [ apiError , setApiError ] = useState < string | null > ( null ) ;
19+ const [ view , setView ] = useState < View > ( "dashboard" ) ;
1820 const wsRef = useRef < WebSocket | null > ( null ) ;
1921 const reconnectTimer = useRef < ReturnType < typeof setTimeout > > ( undefined ) ;
2022
@@ -80,8 +82,6 @@ export function App() {
8082 } ) ;
8183
8284 fetchSessions ( ) ;
83-
84- // Poll every 15s to pick up closed sessions
8585 const pollInterval = setInterval ( fetchSessions , 15000 ) ;
8686
8787 fetch ( "/api/history?days=30" )
@@ -102,97 +102,7 @@ export function App() {
102102 } , [ connectWs ] ) ;
103103
104104 return (
105- < div style = { { maxWidth : 1200 , margin : "0 auto" , padding : "2rem 1.5rem 3rem" } } >
106- { /* Top bar */ }
107- < header
108- style = { {
109- display : "flex" ,
110- alignItems : "center" ,
111- justifyContent : "space-between" ,
112- marginBottom : "2.5rem" ,
113- paddingBottom : "1.25rem" ,
114- borderBottom : `1px solid rgba(69, 71, 90, 0.3)` ,
115- } }
116- >
117- < div style = { { display : "flex" , alignItems : "baseline" , gap : "0.75rem" } } >
118- < h1
119- style = { {
120- margin : 0 ,
121- fontFamily : "var(--font-display)" ,
122- fontSize : "1.6rem" ,
123- fontWeight : 800 ,
124- color : "var(--ctp-lavender)" ,
125- letterSpacing : "-0.02em" ,
126- } }
127- >
128- Claude Monitor
129- </ h1 >
130- { sessions . length > 0 && (
131- < span
132- style = { {
133- background : "rgba(166, 227, 161, 0.15)" ,
134- color : "var(--ctp-green)" ,
135- fontFamily : "var(--font-mono)" ,
136- fontSize : "0.7rem" ,
137- fontWeight : 600 ,
138- padding : "0.2rem 0.6rem" ,
139- borderRadius : 20 ,
140- border : "1px solid rgba(166, 227, 161, 0.2)" ,
141- } }
142- >
143- { sessions . length } active
144- </ span >
145- ) }
146- </ div >
147- < div style = { { display : "flex" , alignItems : "center" , gap : "0.75rem" } } >
148- { window . __SNAPSHOT__ ? (
149- < span
150- style = { {
151- fontFamily : "var(--font-mono)" ,
152- fontSize : "0.6rem" ,
153- color : "var(--ctp-yellow)" ,
154- } }
155- title = { `Snapshot captured ${ new Date ( window . __SNAPSHOT__ . capturedAt ) . toLocaleString ( ) } ` }
156- >
157- snapshot · { new Date ( window . __SNAPSHOT__ . capturedAt ) . toLocaleDateString ( ) }
158- </ span >
159- ) : (
160- < span
161- style = { {
162- display : "flex" ,
163- alignItems : "center" ,
164- gap : "0.3rem" ,
165- fontFamily : "var(--font-mono)" ,
166- fontSize : "0.6rem" ,
167- color : wsStatus === "connected" ? "var(--ctp-green)"
168- : wsStatus === "connecting" ? "var(--ctp-yellow)"
169- : "var(--ctp-red)" ,
170- } }
171- title = { `WebSocket: ${ wsStatus } ` }
172- >
173- < span style = { {
174- width : 6 ,
175- height : 6 ,
176- borderRadius : "50%" ,
177- background : "currentColor" ,
178- display : "inline-block" ,
179- } } />
180- { wsStatus === "disconnected" ? "reconnecting…" : "live" }
181- </ span >
182- ) }
183- < span
184- style = { {
185- fontFamily : "var(--font-mono)" ,
186- fontSize : "0.65rem" ,
187- color : "var(--ctp-overlay0)" ,
188- letterSpacing : "0.05em" ,
189- } }
190- >
191- powered by bun
192- </ span >
193- </ div >
194- </ header >
195-
105+ < div >
196106 { /* Error banner */ }
197107 { apiError && (
198108 < div
@@ -201,7 +111,7 @@ export function App() {
201111 border : "1px solid rgba(243, 139, 168, 0.3)" ,
202112 borderRadius : 10 ,
203113 padding : "0.6rem 1rem" ,
204- marginBottom : "1.5rem" ,
114+ margin : "1rem 1.5rem 0 " ,
205115 fontFamily : "var(--font-mono)" ,
206116 fontSize : "0.75rem" ,
207117 color : "var(--ctp-red)" ,
@@ -212,47 +122,54 @@ export function App() {
212122 </ div >
213123 ) }
214124
215- { /* Live Sessions */ }
216- < section style = { { marginBottom : "2.5rem" } } >
217- < h2 className = "section-title" > Live Sessions</ h2 >
218- < LiveSessions sessions = { sessions } />
219- </ section >
220-
221- { /* Rate Limits */ }
222- < section style = { { marginBottom : "2rem" } } >
223- < h2 className = "section-title" > Rate Limits</ h2 >
224- < div className = "card" >
225- < RateLimits />
125+ { /* Navigation */ }
126+ < div style = { { maxWidth : 1200 , margin : "0 auto" , padding : "1rem 1.5rem 0" } } >
127+ < nav className = "ov-nav" >
128+ < button
129+ className = { `ov-nav-btn ${ view === "dashboard" ? "ov-nav-btn--active" : "" } ` }
130+ onClick = { ( ) => setView ( "dashboard" ) }
131+ >
132+ Dashboard
133+ </ button >
134+ < button
135+ className = { `ov-nav-btn ${ view === "analytics" ? "ov-nav-btn--active" : "" } ` }
136+ onClick = { ( ) => setView ( "analytics" ) }
137+ >
138+ Analytics
139+ </ button >
140+ </ nav >
141+ </ div >
142+
143+ { view === "dashboard" ? (
144+ < >
145+ < OverviewDashboard
146+ activeSessions = { sessions . length }
147+ wsStatus = { wsStatus }
148+ />
149+
150+ { /* Live Sessions — show below dashboard when active */ }
151+ { sessions . length > 0 && (
152+ < div style = { { maxWidth : 1200 , margin : "0 auto" , padding : "0 1.5rem 2rem" } } >
153+ < h2 className = "section-title" > Live Sessions</ h2 >
154+ < LiveSessions sessions = { sessions } />
155+ </ div >
156+ ) }
157+ </ >
158+ ) : (
159+ < div style = { { maxWidth : 1200 , margin : "0 auto" , padding : "1rem 1.5rem 3rem" } } >
160+ { /* Stats */ }
161+ < section style = { { marginBottom : "2rem" } } >
162+ < h2 className = "section-title" > Usage Statistics</ h2 >
163+ < StatsTabs onStatsChange = { setCurrentStats } onPeriodChange = { setPeriod } />
164+ </ section >
165+
166+ { /* Analytics */ }
167+ < section style = { { marginBottom : "2.5rem" } } >
168+ < h2 className = "section-title" > Analytics</ h2 >
169+ < AnalyticsTabs period = { period } currentStats = { currentStats } history = { history } />
170+ </ section >
226171 </ div >
227- </ section >
228-
229- { /* Stats with comparison badges */ }
230- < section style = { { marginBottom : "2rem" } } >
231- < h2 className = "section-title" > Usage Statistics</ h2 >
232- < StatsTabs onStatsChange = { setCurrentStats } onPeriodChange = { setPeriod } />
233- </ section >
234-
235- { /* Analytics */ }
236- < section style = { { marginBottom : "2.5rem" } } >
237- < h2 className = "section-title" > Analytics</ h2 >
238- < AnalyticsTabs period = { period } currentStats = { currentStats } history = { history } />
239- </ section >
240-
241- { /* Footer */ }
242- < footer
243- style = { {
244- marginTop : "3rem" ,
245- paddingTop : "1.25rem" ,
246- borderTop : "1px solid rgba(69, 71, 90, 0.3)" ,
247- textAlign : "center" ,
248- fontFamily : "var(--font-mono)" ,
249- fontSize : "0.7rem" ,
250- color : "var(--ctp-overlay0)" ,
251- letterSpacing : "0.03em" ,
252- } }
253- >
254- made with AI < span style = { { color : "var(--ctp-red)" , fontSize : "0.85rem" } } > ♥</ span >
255- </ footer >
172+ ) }
256173 </ div >
257174 ) ;
258175}
0 commit comments