11import { useEffect , useMemo , useState } from 'react' ;
2- import { fetchToolsTrend , type ToolTrendResponse } from '../api' ;
2+ import { fetchToolsBucket , fetchToolsTrend , type BucketHit , type ToolTrendResponse } from '../api' ;
33import { useT } from '../i18n' ;
44
55const COLORS = [
@@ -14,12 +14,15 @@ const RANGES: { key: '24h' | '7d' | '30d'; hours: number }[] = [
1414 { key : '30d' , hours : 720 } ,
1515] ;
1616
17- export function ToolTrend ( ) {
17+ export function ToolTrend ( { onOpenSession } : { onOpenSession ?: ( id : string ) => void } ) {
1818 const { t } = useT ( ) ;
1919 const [ range , setRange ] = useState < '24h' | '7d' | '30d' > ( '7d' ) ;
2020 const [ data , setData ] = useState < ToolTrendResponse | null > ( null ) ;
2121 const [ loading , setLoading ] = useState ( false ) ;
2222 const [ hover , setHover ] = useState < { idx : number ; x : number ; y : number } | null > ( null ) ;
23+ const [ drill , setDrill ] = useState < { idx : number ; since : string ; until : string ; hits : BucketHit [ ] | null } > (
24+ { idx : - 1 , since : '' , until : '' , hits : null } ,
25+ ) ;
2326
2427 useEffect ( ( ) => {
2528 setLoading ( true ) ;
@@ -118,6 +121,18 @@ export function ToolTrend() {
118121 return (
119122 < g
120123 key = { i }
124+ style = { { cursor : 'pointer' } }
125+ onClick = { ( ) => {
126+ const bucketHourSpan = data . hours / data . totals . length ;
127+ const untilTs = endTs - ( data . totals . length - 1 - i ) * 3600_000 ;
128+ const sinceTs = untilTs - bucketHourSpan * 3600_000 ;
129+ const since = new Date ( sinceTs ) . toISOString ( ) ;
130+ const until = new Date ( untilTs ) . toISOString ( ) ;
131+ setDrill ( { idx : i , since, until, hits : null } ) ;
132+ fetchToolsBucket ( since , until )
133+ . then ( ( hits ) => setDrill ( { idx : i , since, until, hits } ) )
134+ . catch ( ( ) => setDrill ( { idx : i , since, until, hits : [ ] } ) ) ;
135+ } }
121136 onMouseEnter = { ( e ) => {
122137 const rect = ( e . currentTarget . ownerSVGElement as SVGSVGElement ) . getBoundingClientRect ( ) ;
123138 setHover ( {
@@ -201,6 +216,45 @@ export function ToolTrend() {
201216 </ div >
202217 ) ) }
203218 </ div >
219+
220+ { drill . idx >= 0 && (
221+ < div className = "mt-4 border-t border-slate-800 pt-3" >
222+ < div className = "flex items-center gap-2 text-[11px] text-slate-400 mb-2" >
223+ < span className = "font-mono text-slate-200" >
224+ { new Date ( drill . since ) . toLocaleString ( [ ] , { month : 'short' , day : 'numeric' , hour : '2-digit' , minute : '2-digit' } ) }
225+ { ' → ' }
226+ { new Date ( drill . until ) . toLocaleString ( [ ] , { hour : '2-digit' , minute : '2-digit' } ) }
227+ </ span >
228+ < span className = "text-slate-500" > ·</ span >
229+ < span > { drill . hits ? `${ drill . hits . length } sessions` : '…' } </ span >
230+ < button
231+ onClick = { ( ) => setDrill ( { idx : - 1 , since : '' , until : '' , hits : null } ) }
232+ className = "ml-auto text-slate-500 hover:text-slate-200 text-xs"
233+ > ✕</ button >
234+ </ div >
235+ { drill . hits && drill . hits . length === 0 ? (
236+ < div className = "text-[11px] text-slate-600 py-2" > no sessions in this bucket</ div >
237+ ) : (
238+ < ul className = "divide-y divide-slate-800/60" >
239+ { ( drill . hits ?? [ ] ) . map ( ( h ) => (
240+ < li
241+ key = { h . session_id }
242+ onClick = { ( ) => onOpenSession ?.( h . session_id ) }
243+ className = "py-1.5 px-1 flex items-center gap-2 cursor-pointer hover:bg-slate-800/40 rounded"
244+ >
245+ < span className = "text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded bg-slate-800/70 text-slate-300 font-mono" >
246+ { h . agent }
247+ </ span >
248+ < span className = "text-[11px] text-slate-300 font-mono truncate flex-1" >
249+ { h . cwd ?? h . session_id }
250+ </ span >
251+ < span className = "text-[11px] tabular-nums text-cyan-300" > { h . count } </ span >
252+ </ li >
253+ ) ) }
254+ </ ul >
255+ ) }
256+ </ div >
257+ ) }
204258 </ >
205259 ) }
206260 </ div >
0 commit comments