@@ -38,15 +38,22 @@ export const AdminPerformancePage: React.FC = () => {
3838 usersApi . getAll ( )
3939 ] ) ;
4040
41- // 1. Initialize stats map with ALL users using ID as key
42- const statsMap : Record < number , UserStats > = { } ;
41+ console . log ( 'Leaderboard Data - Users:' , users . length , 'Tasks:' , tasks . length ) ;
42+
43+ // 1. Initialize stats map with ALL users using email as key (for backward compatibility)
44+ const statsMap : Record < string , UserStats > = { } ;
4345
4446 users . forEach ( u => {
45- const formattedRole = u . role . replace ( 'ROLE_' , '' ) . split ( '_' )
47+ const formattedRole = u . role ? u . role . replace ( 'ROLE_' , '' ) . split ( '_' )
4648 . map ( word => word . charAt ( 0 ) + word . slice ( 1 ) . toLowerCase ( ) )
47- . join ( ' ' ) ;
49+ . join ( ' ' ) : 'Unknown' ;
50+
51+ if ( ! u . email ) {
52+ console . warn ( 'Skipping user without email:' , u . name || u . id ) ;
53+ return ; // Skip users without email
54+ }
4855
49- statsMap [ u . id ] = {
56+ statsMap [ u . email ] = {
5057 id : u . id ,
5158 name : u . name ,
5259 email : u . email ,
@@ -68,8 +75,8 @@ export const AdminPerformancePage: React.FC = () => {
6875 if ( task . assignedToList && task . assignedToList . length > 0 ) {
6976 // For multi-assignee tasks, count for ALL assignees
7077 task . assignedToList . forEach ( assigneeEmail => {
71- // Find user by email
72- const matchedStats = Object . values ( statsMap ) . find ( s => s . email === assigneeEmail ) ;
78+ // Use email as key to find user stats directly
79+ const matchedStats = statsMap [ assigneeEmail ] ;
7380
7481 if ( matchedStats ) {
7582 matchedStats . total ++ ;
@@ -86,11 +93,20 @@ export const AdminPerformancePage: React.FC = () => {
8693 // Fallback to old single-assignee logic for backward compatibility
8794 let matchedStats : UserStats | undefined ;
8895
89- if ( task . assigneeId && statsMap [ task . assigneeId ] ) {
90- matchedStats = statsMap [ task . assigneeId ] ;
91- } else if ( task . assignedTo && task . assignedTo !== 'Unassigned' ) {
92- // Fallback for legacy tasks without ID
93- matchedStats = Object . values ( statsMap ) . find ( s => s . name === task . assignedTo ) ;
96+ // Try to find by assigneeId first (look up email by id)
97+ if ( task . assigneeId ) {
98+ matchedStats = Object . values ( statsMap ) . find ( s => s . id === task . assigneeId ) ;
99+ }
100+
101+ // If not found, try matching by name
102+ if ( ! matchedStats && task . assignedTo && task . assignedTo !== 'Unassigned' ) {
103+ // Try exact email match first (in case assignedTo is an email)
104+ matchedStats = statsMap [ task . assignedTo ] ;
105+
106+ // If not found, try name match as final fallback
107+ if ( ! matchedStats ) {
108+ matchedStats = Object . values ( statsMap ) . find ( s => s . name === task . assignedTo ) ;
109+ }
94110 }
95111
96112 if ( matchedStats ) {
@@ -113,6 +129,9 @@ export const AdminPerformancePage: React.FC = () => {
113129 completionRate : s . total > 0 ? ( s . completed / s . total ) * 100 : 0
114130 } ) ) ;
115131
132+ console . log ( 'Leaderboard - Total users in statsMap:' , Object . keys ( statsMap ) . length ) ;
133+ console . log ( 'Leaderboard - Final stats array:' , finalStats . length ) ;
134+
116135 // 4. SORTING LOGIC: Completed Volume (Desc) -> Efficiency Score (Desc) -> Total Assigned (Desc)
117136 // This ensures users with high output rank higher than users with low volume but 100% rate.
118137 finalStats . sort ( ( a , b ) => {
@@ -128,6 +147,7 @@ export const AdminPerformancePage: React.FC = () => {
128147 return b . total - a . total ;
129148 } ) ;
130149
150+ console . log ( 'Leaderboard - Setting stats with' , finalStats . length , 'users' ) ;
131151 setStats ( finalStats ) ;
132152 } catch ( e ) {
133153 console . error ( e ) ;
@@ -239,11 +259,23 @@ export const AdminPerformancePage: React.FC = () => {
239259 </ div >
240260 ) : (
241261 < >
242- { /* Podium - Only show if we have data */ }
243- { topPerformers . length > 0 && stats . length >= 3 && (
244- < div className = "mb-16 lg:mb-24 px-4" >
245- < div className = "flex flex-col md:flex-row justify-center items-end gap-6 md:gap-10 max-w-5xl mx-auto" >
246- { [ topPerformers [ 1 ] , topPerformers [ 0 ] , topPerformers [ 2 ] ] . map ( ( user , i ) => {
262+ { stats . length === 0 ? (
263+ < div className = "text-center py-20" >
264+ < div className = "inline-flex items-center justify-center h-20 w-20 rounded-full bg-slate-100 mb-6" >
265+ < Trophy className = "h-10 w-10 text-slate-300" />
266+ </ div >
267+ < h3 className = "text-xl font-black text-slate-900 mb-2" > No Performance Data</ h3 >
268+ < p className = "text-slate-500 text-sm" > No users or tasks found. Check your data or try refreshing.</ p >
269+ </ div >
270+ ) : (
271+ < >
272+ { /* Podium - Show if we have at least 1 user */ }
273+ { topPerformers . length > 0 && (
274+ < div className = "mb-16 lg:mb-24 px-4" >
275+ < div className = "flex flex-col md:flex-row justify-center items-end gap-6 md:gap-10 max-w-5xl mx-auto" >
276+ { /* Show top 3 if available, otherwise show what we have */ }
277+ { stats . length >= 3 ? (
278+ [ topPerformers [ 1 ] , topPerformers [ 0 ] , topPerformers [ 2 ] ] . map ( ( user , i ) => {
247279 if ( ! user ) return null ;
248280 const rank = i === 1 ? 0 : i === 0 ? 1 : 2 ;
249281 const config = getRankConfig ( rank ) ;
@@ -295,7 +327,62 @@ export const AdminPerformancePage: React.FC = () => {
295327 </ div >
296328 </ div >
297329 ) ;
298- } ) }
330+ } )
331+ ) : (
332+ // Show available users in order for less than 3 users
333+ topPerformers . map ( ( user , idx ) => {
334+ if ( ! user ) return null ;
335+ const config = getRankConfig ( idx ) ;
336+
337+ return (
338+ < div key = { user . name } className = { `w-full md:w-80 flex flex-col ${ config . containerClass } ` } >
339+ < div
340+ onClick = { ( ) => setSelectedUser ( user ) }
341+ className = { `relative p-8 rounded-[2.5rem] border backdrop-blur-xl flex flex-col items-center text-center transition-all duration-500 hover:-translate-y-2 cursor-pointer group ${ config . cardClass } ` }
342+ >
343+ < div className = "absolute -top-6" >
344+ < div className = "bg-white p-3 rounded-2xl shadow-lg border border-slate-100" >
345+ { config . icon }
346+ </ div >
347+ </ div >
348+
349+ < div className = "mt-8 mb-5 relative" >
350+ < div className = { `p-1.5 rounded-[1.5rem] bg-white shadow-xl ring-4 ${ config . ringColor } ` } >
351+ { user . avatarUrl ? (
352+ < img src = { user . avatarUrl } alt = { user . name } referrerPolicy = "no-referrer" className = "h-20 w-20 rounded-[1.2rem] object-cover" />
353+ ) : (
354+ < div className = "h-20 w-20 rounded-[1.2rem] bg-gradient-to-tr from-slate-100 to-slate-200 flex items-center justify-center text-2xl font-black text-slate-400 uppercase tracking-tighter" >
355+ { user . name . slice ( 0 , 2 ) }
356+ </ div >
357+ ) }
358+ </ div >
359+ < div className = "absolute -bottom-3 inset-x-0 flex justify-center" >
360+ < span className = "bg-slate-900 text-white text-[9px] font-black px-3 py-1 rounded-xl shadow-lg border-2 border-white uppercase tracking-widest" >
361+ { user . completed } Completed
362+ </ span >
363+ </ div >
364+ </ div >
365+
366+ < h3 className = { `text-xl font-black mb-2 truncate w-full tracking-tight ${ config . titleColor } ` } > { user . name } </ h3 >
367+ < span className = { `text-[9px] font-black uppercase tracking-widest px-3 py-1 rounded-lg mb-6 ${ config . badgeClass } ` } >
368+ { user . role }
369+ </ span >
370+
371+ < div className = "grid grid-cols-2 gap-3 w-full mt-auto" >
372+ < div className = "bg-white/60 p-3 rounded-2xl border border-white shadow-inner" >
373+ < p className = "text-[8px] font-black text-slate-400 uppercase tracking-widest mb-1" > Assigned</ p >
374+ < p className = "text-xl font-black text-slate-900" > { user . total } </ p >
375+ </ div >
376+ < div className = "bg-white/60 p-3 rounded-2xl border border-white shadow-inner" >
377+ < p className = "text-[8px] font-black text-slate-400 uppercase tracking-widest mb-1" > Efficiency</ p >
378+ < p className = "text-xl font-black text-slate-900" > { user . completionRate . toFixed ( 0 ) } %</ p >
379+ </ div >
380+ </ div >
381+ </ div >
382+ </ div >
383+ ) ;
384+ } )
385+ ) }
299386 </ div >
300387 </ div >
301388 ) }
@@ -389,14 +476,16 @@ export const AdminPerformancePage: React.FC = () => {
389476 </ div >
390477 </ div >
391478 </ td >
392- </ tr >
479+ </ tr >
393480 ) ) }
394481 </ tbody >
395482 </ table >
396483 </ div >
397484 </ div >
398- </ >
399- ) }
485+ </ >
486+ ) }
487+ </ >
488+ ) }
400489 </ main >
401490 </ div >
402491
0 commit comments