@@ -32,62 +32,32 @@ export async function GET(request: NextRequest) {
3232 return NextResponse . json ( { error : "Unauthorized" } , { status : 401 } ) ;
3333 }
3434
35- // Try to use the optimized database function first
36- const { data : leaders , error } = await supabase
37- . rpc ( 'get_project_leaderboard' , {
38- time_filter : timeFilter ,
39- result_limit : limit
40- } ) ;
41-
42- if ( error ) {
43- // Fallback to manual query if RPC doesn't exist
44- console . warn ( "RPC function not found, using fallback query:" , error ) ;
45-
46- // Optimized fallback - single query with joins
47- let baseQuery = `
48- projects!inner(*),
49- project_members!inner(
50- codev_id,
51- codev_points!inner(
52- points,
53- created_at,
54- skill_category:skill_category_id!inner(name)
55- )
56- )
57- ` ;
35+ let processedLeaders : ProjectLeader [ ] = [ ] ;
5836
59- let query = supabase
37+ if ( timeFilter === "all" ) {
38+ // All-time leaderboard uses the codev_points/project_members relationship for historical data
39+ const { data : rawData , error : fallbackError } = await supabase
6040 . from ( "projects" )
61- . select ( baseQuery ) ;
62-
63- // Apply time filters through the relationship
64- if ( timeFilter === "weekly" ) {
65- const weekStart = new Date ( ) ;
66- weekStart . setDate ( weekStart . getDate ( ) - weekStart . getDay ( ) ) ;
67- weekStart . setHours ( 0 , 0 , 0 , 0 ) ;
68- query = query . gte ( "project_members.codev_points.created_at" , weekStart . toISOString ( ) ) ;
69- } else if ( timeFilter === "monthly" ) {
70- const monthStart = new Date ( ) ;
71- monthStart . setDate ( 1 ) ;
72- monthStart . setHours ( 0 , 0 , 0 , 0 ) ;
73- query = query . gte ( "project_members.codev_points.created_at" , monthStart . toISOString ( ) ) ;
74- }
75-
76- const { data : rawData , error : fallbackError } = await query . limit ( 100 ) ; // Get more projects to filter later
41+ . select ( `
42+ id,
43+ name,
44+ project_members(
45+ codev_id,
46+ codev_points(
47+ points,
48+ skill_category:skill_category_id(name)
49+ )
50+ )
51+ ` ) ;
7752
7853 if ( fallbackError ) {
79- console . error ( "Error fetching project leaderboard:" , fallbackError ) ;
54+ console . error ( "Error fetching all-time project leaderboard:" , fallbackError ) ;
8055 return NextResponse . json (
8156 { error : "Failed to fetch leaderboard data" , details : fallbackError . message } ,
8257 { status : 500 }
8358 ) ;
8459 }
8560
86- if ( ! rawData || rawData . length === 0 ) {
87- return NextResponse . json ( { leaders : [ ] , totalCount : 0 } ) ;
88- }
89-
90- // Process the complex nested data structure
9161 const projectMap = new Map < string , {
9262 project_id : string ;
9363 project_name : string ;
@@ -96,11 +66,8 @@ export async function GET(request: NextRequest) {
9666 skill_breakdown : Record < string , number > ;
9767 } > ( ) ;
9868
99- rawData . forEach ( ( project : any ) => {
100- if ( ! project . project_members || project . project_members . length === 0 ) return ;
101-
69+ rawData ?. forEach ( ( project : any ) => {
10270 const projectId = project . id ;
103-
10471 if ( ! projectMap . has ( projectId ) ) {
10572 projectMap . set ( projectId , {
10673 project_id : projectId ,
@@ -110,63 +77,126 @@ export async function GET(request: NextRequest) {
11077 skill_breakdown : { }
11178 } ) ;
11279 }
113-
11480 const projectData = projectMap . get ( projectId ) ! ;
11581
116- project . project_members . forEach ( ( member : any ) => {
82+ project . project_members ? .forEach ( ( member : any ) => {
11783 projectData . members . add ( member . codev_id ) ;
118-
119- if ( member . codev_points && member . codev_points . length > 0 ) {
120- member . codev_points . forEach ( ( point : any ) => {
121- const skillName = point . skill_category ?. name || "Other" ;
122- const pointValue = point . points || 0 ;
123-
124- projectData . total_points += pointValue ;
125- projectData . skill_breakdown [ skillName ] =
126- ( projectData . skill_breakdown [ skillName ] || 0 ) + pointValue ;
127- } ) ;
128- }
84+ member . codev_points ?. forEach ( ( point : any ) => {
85+ const skillName = point . skill_category ?. name || "Other" ;
86+ const val = point . points || 0 ;
87+ projectData . total_points += val ;
88+ projectData . skill_breakdown [ skillName ] = ( projectData . skill_breakdown [ skillName ] || 0 ) + val ;
89+ } ) ;
12990 } ) ;
13091 } ) ;
13192
132- // Convert to array and sort
133- const processedLeaders : ProjectLeader [ ] = Array . from ( projectMap . values ( ) )
134- . map ( project => ( {
135- project_id : project . project_id ,
136- project_name : project . project_name ,
137- total_points : project . total_points ,
138- member_count : project . members . size ,
139- skill_breakdown : project . skill_breakdown
93+ processedLeaders = Array . from ( projectMap . values ( ) )
94+ . map ( p => ( {
95+ project_id : p . project_id ,
96+ project_name : p . project_name ,
97+ total_points : p . total_points ,
98+ member_count : p . members . size ,
99+ skill_breakdown : p . skill_breakdown
140100 } ) )
141- . filter ( project => project . total_points > 0 )
142- . sort ( ( a , b ) => {
143- if ( b . total_points === a . total_points ) {
144- return a . project_name . localeCompare ( b . project_name ) ;
145- }
146- return b . total_points - a . total_points ;
147- } )
101+ . filter ( p => p . total_points > 0 )
102+ . sort ( ( a , b ) => b . total_points - a . total_points )
148103 . slice ( 0 , limit ) ;
149104
150- return NextResponse . json ( {
151- leaders : processedLeaders ,
152- totalCount : processedLeaders . length
105+ } else {
106+ // Weekly/Monthly leaderboard calculates points from completed tasks based on approval date (updated_at)
107+ const startDate = new Date ( ) ;
108+ if ( timeFilter === "weekly" ) {
109+ startDate . setDate ( startDate . getDate ( ) - startDate . getDay ( ) ) ;
110+ startDate . setHours ( 0 , 0 , 0 , 0 ) ;
111+ } else {
112+ startDate . setDate ( 1 ) ;
113+ startDate . setHours ( 0 , 0 , 0 , 0 ) ;
114+ }
115+
116+ const { data : rawTasks , error : tasksError } = await supabase
117+ . from ( "tasks" )
118+ . select ( `
119+ points,
120+ updated_at,
121+ project_id,
122+ codev_id,
123+ sidekick_ids,
124+ project:project_id!inner(name),
125+ skill_category:skill_category_id!inner(name)
126+ ` )
127+ . eq ( "is_archive" , true )
128+ . gte ( "updated_at" , startDate . toISOString ( ) ) ;
129+
130+ if ( tasksError ) {
131+ console . error ( `Error fetching ${ timeFilter } project leaderboard tasks:` , tasksError ) ;
132+ return NextResponse . json (
133+ { error : "Failed to fetch tasks data" , details : tasksError . message } ,
134+ { status : 500 }
135+ ) ;
136+ }
137+
138+ const projectMap = new Map < string , {
139+ project_id : string ;
140+ project_name : string ;
141+ total_points : number ;
142+ members : Set < string > ;
143+ skill_breakdown : Record < string , number > ;
144+ } > ( ) ;
145+
146+ rawTasks ?. forEach ( ( task : any ) => {
147+ const projectId = task . project_id ;
148+ if ( ! projectId ) return ;
149+
150+ if ( ! projectMap . has ( projectId ) ) {
151+ projectMap . set ( projectId , {
152+ project_id : projectId ,
153+ project_name : task . project ?. name || "Unknown Project" ,
154+ total_points : 0 ,
155+ members : new Set ( ) ,
156+ skill_breakdown : { }
157+ } ) ;
158+ }
159+ const projectData = projectMap . get ( projectId ) ! ;
160+ const skillName = task . skill_category ?. name || "Other" ;
161+ const points = task . points || 0 ;
162+ const sidekickPoints = Math . floor ( points * 0.5 ) ;
163+
164+ // Add primary assignee points
165+ if ( task . codev_id ) {
166+ projectData . members . add ( task . codev_id ) ;
167+ projectData . total_points += points ;
168+ projectData . skill_breakdown [ skillName ] = ( projectData . skill_breakdown [ skillName ] || 0 ) + points ;
169+ }
170+
171+ // Add sidekick points
172+ if ( task . sidekick_ids && Array . isArray ( task . sidekick_ids ) ) {
173+ task . sidekick_ids . forEach ( ( sid : string ) => {
174+ projectData . members . add ( sid ) ;
175+ projectData . total_points += sidekickPoints ;
176+ projectData . skill_breakdown [ skillName ] = ( projectData . skill_breakdown [ skillName ] || 0 ) + sidekickPoints ;
177+ } ) ;
178+ }
153179 } ) ;
154- }
155180
156- // Process RPC results
157- const processedLeaders = leaders ?. map ( ( leader : any ) => ( {
158- project_id : leader . project_id ,
159- project_name : leader . project_name || "Unknown Project" ,
160- total_points : leader . total_points || 0 ,
161- member_count : leader . member_count || 0 ,
162- skill_breakdown : leader . skill_breakdown || { }
163- } ) ) || [ ] ;
181+ processedLeaders = Array . from ( projectMap . values ( ) )
182+ . map ( p => ( {
183+ project_id : p . project_id ,
184+ project_name : p . project_name ,
185+ total_points : p . total_points ,
186+ member_count : p . members . size ,
187+ skill_breakdown : p . skill_breakdown
188+ } ) )
189+ . filter ( p => p . total_points > 0 )
190+ . sort ( ( a , b ) => b . total_points - a . total_points )
191+ . slice ( 0 , limit ) ;
192+ }
164193
165194 return NextResponse . json ( {
166195 leaders : processedLeaders ,
167196 totalCount : processedLeaders . length
168197 } ) ;
169198
199+
170200 } catch ( error ) {
171201 console . error ( "API error:" , error ) ;
172202
0 commit comments