@@ -25,6 +25,7 @@ interface ModelCost {
2525 avg_spend : number ;
2626 total_spend : number ;
2727 total_reqs : number ;
28+ emails : string [ ] ;
2829}
2930
3031const TIME_RANGES = [
@@ -75,6 +76,10 @@ export function DashboardClient({ initialData }: DashboardClientProps) {
7576 label : string ;
7677 emails : Set < string > ;
7778 } | null > ( null ) ;
79+ const [ modelFilter , setModelFilter ] = useState < {
80+ model : string ;
81+ emails : Set < string > ;
82+ } | null > ( null ) ;
7883
7984 const groupParam = searchParams . get ( "group" ) ;
8085 const exhaustionParam = searchParams . get ( "exhaustion" ) ;
@@ -200,6 +205,9 @@ export function DashboardClient({ initialData }: DashboardClientProps) {
200205 if ( exhaustionFilter ) {
201206 users = users . filter ( ( u ) => exhaustionFilter . emails . has ( u . email ) ) ;
202207 }
208+ if ( modelFilter ) {
209+ users = users . filter ( ( u ) => modelFilter . emails . has ( u . email ) ) ;
210+ }
203211 if ( search . trim ( ) ) {
204212 const q = search . toLowerCase ( ) ;
205213 users = users . filter (
@@ -240,7 +248,16 @@ export function DashboardClient({ initialData }: DashboardClientProps) {
240248 }
241249 } ) ;
242250 return sorted ;
243- } , [ stats . rankedUsers , search , sortCol , sortAsc , groupEmailSet , badgeFilter , exhaustionFilter ] ) ;
251+ } , [
252+ stats . rankedUsers ,
253+ search ,
254+ sortCol ,
255+ sortAsc ,
256+ groupEmailSet ,
257+ badgeFilter ,
258+ exhaustionFilter ,
259+ modelFilter ,
260+ ] ) ;
244261
245262 const searchedUser = useMemo ( ( ) => {
246263 if ( ! search . trim ( ) ) return null ;
@@ -270,7 +287,8 @@ export function DashboardClient({ initialData }: DashboardClientProps) {
270287 search . trim ( ) . length > 0 ||
271288 selectedGroup !== "all" ||
272289 badgeFilter !== null ||
273- exhaustionFilter !== null ;
290+ exhaustionFilter !== null ||
291+ modelFilter !== null ;
274292 const totalLines = stats . dailyTeamActivity . reduce ( ( s , d ) => s + d . total_lines_added , 0 ) ;
275293 const effectiveDays = Math . min ( days , stats . cycleDays ) ;
276294 const cycleStartDate = new Date ( stats . cycleStart ) ;
@@ -409,6 +427,17 @@ export function DashboardClient({ initialData }: DashboardClientProps) {
409427 </ button >
410428 </ span >
411429 ) }
430+ { modelFilter && (
431+ < span className = "inline-flex items-center gap-1.5 bg-purple-600/20 text-purple-300 rounded-md px-2 py-1 text-[11px] font-medium" >
432+ Model: { shortModel ( modelFilter . model ) } ({ modelFilter . emails . size } )
433+ < button
434+ onClick = { ( ) => setModelFilter ( null ) }
435+ className = "hover:text-purple-100 cursor-pointer"
436+ >
437+ ✕
438+ </ button >
439+ </ span >
440+ ) }
412441 < div className = "ml-auto text-[11px] text-zinc-600" >
413442 { isSearching ? `${ filteredUsers . length } / ` : "" }
414443 { stats . totalMembers } members
@@ -501,7 +530,15 @@ export function DashboardClient({ initialData }: DashboardClientProps) {
501530 />
502531 </ ExpandableCard >
503532 < ExpandableCard >
504- < ModelCostComparison data = { data . modelCosts } />
533+ < ModelCostComparison
534+ data = { data . modelCosts }
535+ activeModel = { modelFilter ?. model ?? null }
536+ onModelFilter = { ( model , emails ) => {
537+ setModelFilter ( ( prev ) =>
538+ prev ?. model === model ? null : { model, emails : new Set ( emails ) } ,
539+ ) ;
540+ } }
541+ />
505542 </ ExpandableCard >
506543 </ div >
507544
@@ -604,7 +641,15 @@ function buildDailySpendData(breakdown: SpendBreakdownRow[]): {
604641 } ;
605642}
606643
607- function ModelCostComparison ( { data } : { data : ModelCost [ ] } ) {
644+ function ModelCostComparison ( {
645+ data,
646+ activeModel,
647+ onModelFilter,
648+ } : {
649+ data : ModelCost [ ] ;
650+ activeModel : string | null ;
651+ onModelFilter : ( model : string , emails : string [ ] ) => void ;
652+ } ) {
608653 const rows = data
609654 . filter ( ( d ) => d . total_reqs > 0 )
610655 . map ( ( d ) => ( {
@@ -651,7 +696,10 @@ function ModelCostComparison({ data }: { data: ModelCost[] }) {
651696 const mult = cheapest > 0 ? row . costPerReq / cheapest : 1 ;
652697 const barPct = maxCostPerReq > 0 ? ( row . costPerReq / maxCostPerReq ) * 100 : 0 ;
653698 return (
654- < tr key = { row . model } className = "border-b border-zinc-800/30 hover:bg-zinc-800/30" >
699+ < tr
700+ key = { row . model }
701+ className = { `border-b border-zinc-800/30 hover:bg-zinc-800/30 ${ activeModel === row . model ? "bg-purple-500/10" : "" } ` }
702+ >
655703 < td className = "py-1 text-zinc-300 font-mono cursor-default" title = { row . model } >
656704 { shortModel ( row . model ) }
657705 </ td >
@@ -671,7 +719,15 @@ function ModelCostComparison({ data }: { data: ModelCost[] }) {
671719 </ span >
672720 </ div >
673721 </ td >
674- < td className = "text-right py-1 text-zinc-400" > { row . users } </ td >
722+ < td className = "text-right py-1" >
723+ < button
724+ onClick = { ( ) => onModelFilter ( row . model , row . emails ) }
725+ className = { `font-mono cursor-pointer hover:text-purple-300 transition-colors ${ activeModel === row . model ? "text-purple-400 font-bold" : "text-zinc-400" } ` }
726+ title = { `Filter by ${ shortModel ( row . model ) } users` }
727+ >
728+ { row . users }
729+ </ button >
730+ </ td >
675731 < td className = "text-right py-1 font-mono text-zinc-400" >
676732 ${ row . total_spend . toLocaleString ( ) }
677733 </ td >
0 commit comments