1+ import { useQuery } from '@tanstack/react-query' ;
2+ import { base44 } from '@/api/base44Client' ;
3+ import { Card , CardContent , CardHeader , CardTitle , CardDescription } from '@/components/ui/card' ;
4+ import { Badge } from '@/components/ui/badge' ;
5+ import { Avatar } from '@/components/ui/avatar' ;
6+ import { Tabs , TabsList , TabsTrigger , TabsContent } from '@/components/ui/tabs' ;
7+ import { Trophy , TrendingUp , Users , Award } from 'lucide-react' ;
8+ import LoadingSpinner from '@/components/common/LoadingSpinner' ;
9+
10+ export default function TeamWellnessLeaderboard ( { challengeId } ) {
11+ const { data : challenge } = useQuery ( {
12+ queryKey : [ 'wellnessChallenge' , challengeId ] ,
13+ queryFn : ( ) => base44 . entities . WellnessChallenge . filter ( { id : challengeId } ) . then ( r => r [ 0 ] )
14+ } ) ;
15+
16+ const { data : leaderboard , isLoading } = useQuery ( {
17+ queryKey : [ 'teamWellnessLeaderboard' , challengeId ] ,
18+ queryFn : async ( ) => {
19+ const goals = await base44 . entities . WellnessGoal . filter ( { challenge_id : challengeId } ) ;
20+
21+ // Aggregate by team
22+ const teamStats = { } ;
23+
24+ for ( const goal of goals ) {
25+ const memberships = await base44 . entities . TeamMembership . filter ( {
26+ user_email : goal . user_email
27+ } ) ;
28+
29+ for ( const membership of memberships ) {
30+ if ( ! teamStats [ membership . team_id ] ) {
31+ const team = await base44 . entities . Team . filter ( { id : membership . team_id } ) . then ( r => r [ 0 ] ) ;
32+ teamStats [ membership . team_id ] = {
33+ teamId : membership . team_id ,
34+ teamName : team ?. name || 'Unknown Team' ,
35+ totalProgress : 0 ,
36+ memberCount : 0 ,
37+ completedGoals : 0
38+ } ;
39+ }
40+
41+ teamStats [ membership . team_id ] . totalProgress += goal . progress_percentage || 0 ;
42+ teamStats [ membership . team_id ] . memberCount += 1 ;
43+ if ( goal . status === 'completed' ) {
44+ teamStats [ membership . team_id ] . completedGoals += 1 ;
45+ }
46+ }
47+ }
48+
49+ // Calculate averages and sort
50+ const teams = Object . values ( teamStats ) . map ( team => ( {
51+ ...team ,
52+ avgProgress : team . memberCount > 0 ? team . totalProgress / team . memberCount : 0
53+ } ) ) . sort ( ( a , b ) => b . avgProgress - a . avgProgress ) ;
54+
55+ return teams ;
56+ } ,
57+ enabled : ! ! challengeId
58+ } ) ;
59+
60+ const { data : individualLeaderboard } = useQuery ( {
61+ queryKey : [ 'individualWellnessLeaderboard' , challengeId ] ,
62+ queryFn : async ( ) => {
63+ const goals = await base44 . entities . WellnessGoal . filter ( { challenge_id : challengeId } ) ;
64+ const profiles = await base44 . entities . UserProfile . filter ( { } ) ;
65+
66+ return goals
67+ . map ( goal => ( {
68+ ...goal ,
69+ profile : profiles . find ( p => p . user_email === goal . user_email )
70+ } ) )
71+ . sort ( ( a , b ) => ( b . progress_percentage || 0 ) - ( a . progress_percentage || 0 ) )
72+ . slice ( 0 , 10 ) ;
73+ } ,
74+ enabled : ! ! challengeId
75+ } ) ;
76+
77+ if ( isLoading ) return < LoadingSpinner /> ;
78+
79+ const getRankColor = ( index ) => {
80+ if ( index === 0 ) return 'text-yellow-600' ;
81+ if ( index === 1 ) return 'text-slate-400' ;
82+ if ( index === 2 ) return 'text-amber-600' ;
83+ return 'text-slate-600' ;
84+ } ;
85+
86+ const getRankBg = ( index ) => {
87+ if ( index === 0 ) return 'bg-gradient-to-br from-yellow-400 to-yellow-600' ;
88+ if ( index === 1 ) return 'bg-gradient-to-br from-slate-300 to-slate-400' ;
89+ if ( index === 2 ) return 'bg-gradient-to-br from-amber-400 to-amber-600' ;
90+ return 'bg-slate-100' ;
91+ } ;
92+
93+ return (
94+ < Card >
95+ < CardHeader >
96+ < CardTitle className = "flex items-center gap-2" >
97+ < Trophy className = "h-5 w-5 text-int-gold" />
98+ Wellness Leaderboard
99+ </ CardTitle >
100+ < CardDescription >
101+ { challenge ?. title || 'Challenge' } - Team & Individual Rankings
102+ </ CardDescription >
103+ </ CardHeader >
104+ < CardContent >
105+ < Tabs defaultValue = "teams" >
106+ < TabsList className = "w-full" >
107+ < TabsTrigger value = "teams" className = "flex-1" >
108+ < Users className = "h-4 w-4 mr-2" />
109+ Teams
110+ </ TabsTrigger >
111+ < TabsTrigger value = "individual" className = "flex-1" >
112+ < Award className = "h-4 w-4 mr-2" />
113+ Individual
114+ </ TabsTrigger >
115+ </ TabsList >
116+
117+ < TabsContent value = "teams" className = "space-y-3 mt-4" >
118+ { leaderboard ?. length === 0 ? (
119+ < p className = "text-center text-slate-500 py-8" > No team data yet</ p >
120+ ) : (
121+ leaderboard ?. map ( ( team , index ) => (
122+ < div
123+ key = { team . teamId }
124+ className = { `flex items-center gap-3 p-4 rounded-lg border ${
125+ index < 3 ? 'border-int-gold/30 bg-int-gold/5' : 'border-slate-200'
126+ } `}
127+ >
128+ < div className = { `h-10 w-10 rounded-full ${ getRankBg ( index ) } flex items-center justify-center font-bold text-white` } >
129+ { index + 1 }
130+ </ div >
131+ < div className = "flex-1" >
132+ < p className = "font-semibold" > { team . teamName } </ p >
133+ < p className = "text-sm text-slate-500" >
134+ { team . memberCount } members • { team . completedGoals } completed
135+ </ p >
136+ </ div >
137+ < div className = "text-right" >
138+ < p className = "text-2xl font-bold text-int-orange" >
139+ { Math . round ( team . avgProgress ) } %
140+ </ p >
141+ < p className = "text-xs text-slate-500" > Avg Progress</ p >
142+ </ div >
143+ </ div >
144+ ) )
145+ ) }
146+ </ TabsContent >
147+
148+ < TabsContent value = "individual" className = "space-y-3 mt-4" >
149+ { individualLeaderboard ?. length === 0 ? (
150+ < p className = "text-center text-slate-500 py-8" > No participants yet</ p >
151+ ) : (
152+ individualLeaderboard ?. map ( ( goal , index ) => (
153+ < div
154+ key = { goal . id }
155+ className = { `flex items-center gap-3 p-3 rounded-lg border ${
156+ index < 3 ? 'border-int-gold/30 bg-int-gold/5' : 'border-slate-200'
157+ } `}
158+ >
159+ < div className = { `h-8 w-8 rounded-full ${ getRankBg ( index ) } flex items-center justify-center font-bold text-white text-sm` } >
160+ { index + 1 }
161+ </ div >
162+ < div className = "flex-1" >
163+ < p className = "font-medium text-sm" > { goal . user_email } </ p >
164+ { goal . status === 'completed' && (
165+ < Badge className = "text-xs bg-green-500 text-white mt-1" > Completed</ Badge >
166+ ) }
167+ </ div >
168+ < div className = "text-right" >
169+ < p className = "font-bold text-int-orange" >
170+ { Math . round ( goal . progress_percentage || 0 ) } %
171+ </ p >
172+ </ div >
173+ </ div >
174+ ) )
175+ ) }
176+ </ TabsContent >
177+ </ Tabs >
178+ </ CardContent >
179+ </ Card >
180+ ) ;
181+ }
0 commit comments