@@ -86,6 +86,9 @@ export default function Home() {
8686 const pollRef = useRef < ReturnType < typeof setInterval > | null > ( null ) ;
8787 const logsEndRef = useRef < HTMLDivElement > ( null ) ;
8888 const commitCache = useRef < Map < string , any [ ] > > ( new Map ( ) ) ;
89+ const [ filterLogins , setFilterLogins ] = useState < Set < string > > ( new Set ( ) ) ;
90+ const [ filterQuery , setFilterQuery ] = useState ( '' ) ;
91+ const [ filterOpen , setFilterOpen ] = useState ( false ) ;
8992 const generationRef = useRef ( 0 ) ;
9093 const lastCompletedDevsRef = useRef ( 0 ) ;
9194
@@ -519,7 +522,7 @@ export default function Home() {
519522 < div className = "max-w-7xl mx-auto px-4 py-8" >
520523 { /* Header */ }
521524 < div className = "mb-8" >
522- < h1 className = "text-3xl font-bold tracking-tight text-white" > Glooker</ h1 >
525+ < h1 className = "text-3xl font-bold tracking-tight text-white cursor-pointer hover:text-blue-400 transition-colors" onClick = { ( ) => { setActiveReport ( null ) ; setDevelopers ( [ ] ) ; setProgress ( null ) ; setRunning ( false ) ; stopPolling ( ) ; } } > Glooker</ h1 >
523526 < p className = "text-gray-400 mt-1" > GitHub org developer impact analytics</ p >
524527 </ div >
525528
@@ -1028,8 +1031,72 @@ export default function Home() {
10281031 </ div >
10291032 ) }
10301033
1031- { /* Developer table */ }
1034+ { /* User filter */ }
10321035 { developers . length > 0 && (
1036+ < div className = "mb-3 relative" >
1037+ < div className = "flex items-center gap-2 flex-wrap" >
1038+ { [ ...filterLogins ] . map ( login => {
1039+ const dev = developers . find ( d => d . github_login === login ) ;
1040+ return (
1041+ < span key = { login } className = "inline-flex items-center gap-1.5 bg-blue-600/20 text-blue-300 text-xs font-medium px-2.5 py-1 rounded-lg border border-blue-500/30" >
1042+ { dev ?. avatar_url && < img src = { dev . avatar_url } alt = "" className = "w-4 h-4 rounded-full" /> }
1043+ { dev ?. github_name || login }
1044+ < button onClick = { ( ) => setFilterLogins ( prev => { const n = new Set ( prev ) ; n . delete ( login ) ; return n ; } ) } className = "text-blue-400 hover:text-white ml-0.5" > ×</ button >
1045+ </ span >
1046+ ) ;
1047+ } ) }
1048+ < div className = "relative" >
1049+ < input
1050+ type = "text"
1051+ value = { filterQuery }
1052+ onChange = { e => { setFilterQuery ( e . target . value ) ; setFilterOpen ( true ) ; } }
1053+ onFocus = { ( ) => setFilterOpen ( true ) }
1054+ placeholder = { filterLogins . size > 0 ? 'Add more...' : 'Filter by developer...' }
1055+ className = "bg-gray-800 border border-gray-700 rounded-lg px-3 py-1.5 text-sm text-white placeholder-gray-600 focus:outline-none focus:border-blue-500 w-48"
1056+ />
1057+ { filterOpen && filterQuery . length > 0 && ( ( ) => {
1058+ const q = filterQuery . toLowerCase ( ) ;
1059+ const matches = developers . filter ( d =>
1060+ ! filterLogins . has ( d . github_login ) && (
1061+ d . github_login . toLowerCase ( ) . includes ( q ) ||
1062+ ( d . github_name || '' ) . toLowerCase ( ) . includes ( q )
1063+ )
1064+ ) . slice ( 0 , 8 ) ;
1065+ if ( matches . length === 0 ) return null ;
1066+ return (
1067+ < div className = "absolute z-40 top-full mt-1 left-0 w-64 bg-gray-800 border border-gray-700 rounded-lg shadow-xl overflow-hidden" >
1068+ { matches . map ( d => (
1069+ < button
1070+ key = { d . github_login }
1071+ className = "w-full flex items-center gap-2.5 px-3 py-2 text-left text-sm hover:bg-gray-700 transition-colors"
1072+ onClick = { ( ) => {
1073+ setFilterLogins ( prev => new Set ( prev ) . add ( d . github_login ) ) ;
1074+ setFilterQuery ( '' ) ;
1075+ setFilterOpen ( false ) ;
1076+ } }
1077+ >
1078+ { d . avatar_url && < img src = { d . avatar_url } alt = "" className = "w-5 h-5 rounded-full" /> }
1079+ < div >
1080+ < span className = "text-white" > { d . github_name || d . github_login } </ span >
1081+ { d . github_name && < span className = "text-gray-500 ml-1.5" > @{ d . github_login } </ span > }
1082+ </ div >
1083+ </ button >
1084+ ) ) }
1085+ </ div >
1086+ ) ;
1087+ } ) ( ) }
1088+ </ div >
1089+ { filterLogins . size > 0 && (
1090+ < button onClick = { ( ) => setFilterLogins ( new Set ( ) ) } className = "text-xs text-gray-600 hover:text-gray-400" > Clear all</ button >
1091+ ) }
1092+ </ div >
1093+ </ div >
1094+ ) }
1095+
1096+ { /* Developer table */ }
1097+ { ( ( ) => {
1098+ const filteredDevs = filterLogins . size > 0 ? developers . filter ( d => filterLogins . has ( d . github_login ) ) : developers ;
1099+ return filteredDevs . length > 0 && (
10331100 < div className = "bg-gray-900 rounded-xl overflow-hidden" >
10341101 < table className = "w-full text-sm table-fixed" >
10351102 < thead >
@@ -1046,7 +1113,7 @@ export default function Home() {
10461113 </ tr >
10471114 </ thead >
10481115 < tbody >
1049- { developers . map ( ( dev , i ) => (
1116+ { filteredDevs . map ( ( dev , i ) => (
10501117 < tr
10511118 key = { dev . github_login }
10521119 className = "border-b border-gray-800/50 hover:bg-gray-800/30 transition-colors cursor-pointer"
@@ -1103,7 +1170,8 @@ export default function Home() {
11031170 </ tbody >
11041171 </ table >
11051172 </ div >
1106- ) }
1173+ ) ;
1174+ } ) ( ) }
11071175
11081176 { activeReport && developers . length === 0 && activeReport . status === 'completed' && (
11091177 < div className = "text-center text-gray-500 py-16" >
0 commit comments